天天看點

【2020杭電多校】Total Eclipse 【并查集+思維】

考試不能說明所有問題,但可以說明很多問題。(受教了,~wtcl)

題意:

給一個無向圖,n個點,m條邊,每個點有有一個點權w,我們可以選擇k個互相連通的的點進行減1,問當所有點為0時,操作的最小次數。

思路:

這個題隻要按照正常思路搞其實都能把答案搞出來,隻是正常思路效率不夠。

【2020杭電多校】Total Eclipse 【并查集+思維】

顯然這樣的時間複雜度是,是以我們就需要避免分裂的情況,如何避免分裂,可以想想如果這個是一個有序的序列,從大到小逐漸遞減,是否就可以避免分裂成多個段,顯然是可以的。但是我們拆一個圖,圖自然會分裂成很多段,這樣我們就需要去考慮很多東西,比較麻煩。(逆向思維)我們可以構造一個和這個圖一模一樣的圖,這樣不就不會分裂。

1:有一個疑問(構造圖是否會影響結果呢)?

答:不會。按照樸素算法,我們隻需要找一個極大連通塊,算出它的答案,最後累加即可,也就是說,我們不管先算圖中的哪一極大聯通塊,最終都不會影響答案,呢我們構造圖,就會影響答案嗎?顯然不會。

2:如何構造?

答:為了不影響之後的點,我們從最大的點開始構造,首先加入最大的點,此時極大連通塊的數量為,呢麼我們要讓這個點的權值和下一個點的權值一樣時,則需要貢獻 ,然後加入這個點(下一個點),此時需要判斷有幾個極大連通塊,即判斷的關系,如果他們相連,說明他倆構成了一個極大連通塊,呢麼極大連通塊的數量減1(在沒有判斷新加入這個點前,預設這個點和其他點無關系,),并且權值向圖,貢獻值為,此時圖中所有極大連通塊的點權都為,依次往後構造,即可構造出原圖,并得到最小操作次數。

舉例:

【2020杭電多校】Total Eclipse 【并查集+思維】
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <vector>
#include <map>
#include <queue>
#include <set>
#include <ctime>
#include <cstring>
#include <cstdlib>
#include <math.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 5;
const ll maxn = 1e5 + 5;
struct node
{
    ll v, nex;
} edge[maxn * 8];
ll cnt, head[maxn], vis[maxn];
ll pre[maxn];
void add(ll u, ll v)
{
    edge[cnt].v = v, edge[cnt].nex = head[u];
    head[u] = cnt++;
}
struct vain
{
    ll pos, w;
} a[maxn];
bool cmp(vain x, vain y)
{
    return x.w > y.w;
}
ll f(ll x)
{
    if (x != pre[x])
        pre[x] = f(pre[x]);
    return pre[x];
}
ll join(ll x, ll y)
{
    x = f(x), y = f(y);
    if (x != y)
        pre[x] = y;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        memset(head, -1, sizeof head);
        memset(vis, 0, sizeof vis);
        cnt = 0;
        ll n, m;
        cin >> n >> m;
        for (ll i = 1; i <= n; i++)
        {
            pre[i] = i;
            cin >> a[i].w, a[i].pos = i;
        }
        a[n + 1].w = 0;
        sort(a + 1, a + 1 + n, cmp);
        for (ll i = 0; i < m; i++)
        {
            ll u, v;
            cin >> u >> v;
            add(u, v), add(v, u);
        }
        ll ans = 0, sum = 0;
        for (ll i = 1; i <= n; i++)
        {
            ans++; //連通塊數量++
            ll u = a[i].pos;
            vis[u] = 1;
            for (ll j = head[u]; ~j; j = edge[j].nex)
            {
                ll v = edge[j].v;
                if (vis[v])//判斷目前點和之前已經加入的點是否會形成連通塊
                {
                    if (f(v) != f(u))
                        join(u, v), ans--;
                }
            }
            sum += ans * (a[i].w - a[i + 1].w);
        }
        cout << sum << endl;
    }
}