题意:nnn个点mmm条边的无向图,每个点有一个正点权,每次选择一个连通子图,将里面的权值都减111。求所有点权为000的最小步数。
T≤10,n≤105,m≤2×105T\leq 10,n\leq 10^5,m\leq2\times10^5T≤10,n≤105,m≤2×105
考虑一个贪心:每次一定选择一个极大的连通块。
感性理解很容易,还是证明一下:
假设一个极大连通块SSS,我偏不选,只选择它的子连通块来覆盖整个SSS,答案严格更优。考虑两个连在一起的连通块T1,T2T_1,T_2T1,T2,选择T1∪T2,T1∩T2T_1\cup T_2,T_1\cap T_2T1∪T2,T1∩T2一定不比选T1,T2T_1,T_2T1,T2劣。因为选择的连通块覆盖了整个SSS,所以可以一步步合并出SSS(即任选一个与当前集合相邻的点,将覆盖它的集合与当前集合合并),答案不会更劣,矛盾。
对于一个连通块来说,一定是点按照权值从小到大被删。把操作顺序倒过来,就是把大的结点减小成和小的结点相同,然后一起删掉。
形式化地讲,就是把权值从大到小排序依次加入,并把全场的权值都减到当前权值。用并查集维护连通块个数即可。
复杂度O(nlogn)O(n\log n)O(nlogn)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
#define MAXN 100005
using namespace std;
typedef long long ll;
vector<int> e[MAXN];
int fa[MAXN];
inline int find(const int& x){return fa[x]==x? x:fa[x]=find(fa[x]);}
int a[MAXN],p[MAXN],vis[MAXN];
inline bool cmp(const int& x,const int& y){return a[x]>a[y];}
int main()
{int T;scanf("%d",&T);while (T--){int n,m;scanf("%d%d",&n,&m);for (int i=1;i<=n;i++) e[i].clear(),fa[i]=p[i]=i,vis[i]=0,scanf("%d",&a[i]);for (int i=1;i<=m;i++){int u,v;scanf("%d%d",&u,&v);e[u].push_back(v),e[v].push_back(u);}sort(p+1,p+n+1,cmp);int cur=1;ll ans=0;vis[p[1]]=1;for (int i=2;i<=n;i++){ans+=(ll)cur*(a[p[i-1]]-a[p[i]]);++cur;for (vector<int>::iterator it=e[p[i]].begin();it!=e[p[i]].end();++it){int u=p[i],v=*it;if (!vis[v]) continue;u=find(u),v=find(v);if (u!=v) fa[u]=v,--cur;}vis[p[i]]=1;}ans+=(ll)cur*a[p[n]];printf("%lld\n",ans);}return 0;
}