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

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

思路:

这个题只要按照正常思路搞其实都能把答案搞出来,只是正常思路效率不够。

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


显然这样的时间复杂度是【2020杭电多校】Total Eclipse 【并查集+思维】_连通块_02,所以我们就需要避免分裂的情况,如何避免分裂,可以想想如果这个是一个有序的序列,从大到小逐步递减,是否就可以避免分裂成多个段,显然是可以的。但是我们拆一个图,图自然会分裂成很多段,这样我们就需要去考虑很多东西,比较麻烦。(逆向思维)我们可以构造一个和这个图一模一样的图,这样不就不会分裂。

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

2:如何构造?
答:为了不影响之后的点,我们从最大的点开始构造,首先加入最大的点,此时极大连通块的数量为【2020杭电多校】Total Eclipse 【并查集+思维】_#include_03,呢么我们要让这个点的权值和下一个点的权值一样时,则需要贡献 【2020杭电多校】Total Eclipse 【并查集+思维】_#include_04,然后加入【2020杭电多校】Total Eclipse 【并查集+思维】_连通块_05这个点(下一个点),此时需要判断有几个极大连通块,即判断【2020杭电多校】Total Eclipse 【并查集+思维】_权值_06的关系,如果他们相连,说明他俩构成了一个极大连通块,呢么极大连通块的数量减1(在没有判断新加入这个点前,默认这个点和其他点无关系,【2020杭电多校】Total Eclipse 【并查集+思维】_连通块_07),并且权值向图,贡献值为【2020杭电多校】Total Eclipse 【并查集+思维】_#include_08,此时图中所有极大连通块的点权都为【2020杭电多校】Total Eclipse 【并查集+思维】_#include_09,依次往后构造,即可构造出原图,并得到最小操作次数。

举例:

【2020杭电多校】Total Eclipse 【并查集+思维】_权值_10


【2020杭电多校】Total Eclipse 【并查集+思维】_权值_11

参考代码:

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