Total Eclipse(并查集)
Total Eclipse(并查集)
题目大意:
有一个n个点m条边的无向图,结点从1开始编号到n,每个结点都有一个权值bi,现在希望可以将所有结点的权值改为0。可以进行下面的操作:选择一个正整数k(1<=k<=n),选择k个不同的结点c1, c2, ..., ck(1<=ci
官方题解:
每次一定是选择一个极大连通块,将里面所有数同时减小,直到最小值变成 0,然后将变 成 0 的点删除,分裂成多个连通块再接着做。
为了实现这个策略,可以将整个过程倒过来看,变成按照 b 的值从大到小依次加入每个点。 加入每个点 x 时遍历与 x 相连的所有边 (x,y),如果 y 在 x 之前加入且 x 和 y 不连通则将 x 和 y 合并,并将 y 所在连通块的树根的父亲设为 x,得到一棵有根树。那么每个点 x 在成为最小值之前已经被做了 bfatherx 次操作,所以每个点 x 对答案的贡献为 bx − bfatherx。 使用并查集支持路径压缩,时间复杂度 O((n + m)logn)。
理解:
先是一起对整个极大连通分量中的所有点的权值进行减少,然后有的点权变为0,这个点可看成被删除了,然后这个大的连通分量就变成了许多小的连通分量,然后继续删。
对于一个连通块来说,一定是点按照权值从小到大被删。把操作顺序倒过来,就是把大的结点减小成和小的结点相同,然后一起删掉。
代码如下:
#include <bits/stdc++.h> typedef long long LL; #define pb push_back #define mst(a) memset(a,0,sizeof(a)) const int INF = 0x3f3f3f3f;//0x7fffffff; const double eps = 1e-8; const int mod = 1e9+7; const int maxn = 1e5+10; using namespace std; struct edge { int to; int next; }E[10*maxn];//注意边的条数,无向图至少要开两倍 int head[maxn], tot; void add(int u,int v) { E[tot].to = v; E[tot].next = head[u]; head[u] = tot++; } int n,m; int fa[maxn]; int Find(int x) { return x==fa[x]? x : fa[x]=Find(fa[x]); } void Union(int a,int b) { int aa = Find(a); int bb = Find(b); if(aa!=bb) fa[aa] = bb; } int pre[maxn];//每个点的父亲节点,即之前的连通分量减到了这个点的权值 int rk[maxn];//权值第i大的点的编号 int wake[maxn];//标记每个点是否在连通分量中 int a[maxn];//点的权值 init() { tot = 0; for(int i=0;i<=n;i++) { head[i] = -1; pre[i] = wake[i] = 0; rk[i] = fa[i] = i; } } bool cmp(int x, int y) { return a[x]>a[y]; } int main() { #ifdef DEBUG freopen("sample.txt","r",stdin); //freopen("data.out", "w", stdout); #endif int T; scanf("%d",&T); while(T--) { scanf("%d %d",&n,&m); init(); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=m;i++) { int u,v; scanf("%d %d",&u,&v); add(u,v); add(v,u); } sort(rk+1,rk+1+n,cmp); for(int i=1;i<=n;i++) { int t = rk[i]; //选择当前权值最大的一个点 wake[t] = 1; //将其唤醒 for(int j=head[t];j!=-1;j=E[j].next) { int v = E[j].to; if(!wake[v]) continue; int vv = Find(v); if(vv == t) continue; pre[vv] = fa[vv] = t; } } LL ans = 0; for(int i=1;i<=n;i++) ans += a[i]-a[pre[i]]; //因为之前的连通分量所有点的权值全为a[i],他们加上了pre[i]这个点,权值就全变成a[pre[i]]了,故对ans贡献值为a[i]-a[pre[i]] printf("%lld\n",ans); } return 0; }
-

浙公网安备 33010602011771号