【2020杭电多校】Total Eclipse 【并查集+思维】

考试不能说明所有问题,但可以说明很多问题。(受教了,~wtcl)

题意:
给一个无向图,n个点,m条边,每个点有有一个点权w,我们可以选择k个互相连通的的点进行减1,问当所有点为0时,操作的最小次数。

思路:
这个题只要按照正常思路搞其实都能把答案搞出来,只是正常思路效率不够。
在这里插入图片描述
显然这样的时间复杂度是 O ( N 2 ) O(N^2) O(N2),所以我们就需要避免分裂的情况,如何避免分裂,可以想想如果这个是一个有序的序列,从大到小逐步递减,是否就可以避免分裂成多个段,显然是可以的。但是我们拆一个图,图自然会分裂成很多段,这样我们就需要去考虑很多东西,比较麻烦。(逆向思维)我们可以构造一个和这个图一模一样的图,这样不就不会分裂。

1:有一个疑问(构造图是否会影响结果呢)?
答:不会。按照朴素算法,我们只需要找一个极大连通块,算出它的答案,最后累加即可,也就是说,我们不管先算图中的哪一极大联通块,最终都不会影响答案,呢我们构造图,就会影响答案吗?显然不会。

2:如何构造?
答:为了不影响之后的点,我们从最大的点开始构造,首先加入最大的点,此时极大连通块的数量为 ( a n s = 1 ) (ans=1) (ans=1),呢么我们要让这个点的权值和下一个点的权值一样时,则需要贡献 ( a [ i ] . w − a [ i + 1 ] . w ) ∗ a n s (a[i].w-a[i+1].w)*ans (a[i].wa[i+1].w)ans,然后加入 a [ i + 1 ] . p o s a[i+1].pos a[i+1].pos这个点(下一个点),此时需要判断有几个极大连通块,即判断 a [ i ] . p o s 和 a [ i + 1 ] . p o s a[i].pos和a[i+1].pos a[i].posa[i+1].pos的关系,如果他们相连,说明他俩构成了一个极大连通块,呢么极大连通块的数量减1(在没有判断新加入这个点前,默认这个点和其他点无关系, a n s − 1 ans-1 ans1),并且权值向图,贡献值为 ( a n s − 1 ) ∗ ( a [ i + 1 ] . w − a [ i + 2 ] . w ) (ans-1)*(a[i+1].w-a[i+2].w) (ans1)(a[i+1].wa[i+2].w),此时图中所有极大连通块的点权都为 a [ i + 2 ] . w a[i+2].w a[i+2].w,依次往后构造,即可构造出原图,并得到最小操作次数。

举例:
【2020杭电多校】Total Eclipse 【并查集+思维】_第1张图片
【2020杭电多校】Total Eclipse 【并查集+思维】_第2张图片

参考代码:

#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;
    }
}

你可能感兴趣的:(【2020杭电多校】Total Eclipse 【并查集+思维】)