HDU6763 Total Eclipse(并查集)
问题描述
拜特兰有n个城市和m条双向道路。这些城市被标记为1,2,…,n,第i个城市的亮度是bi。
魔术师Sunset想和拜特兰开个玩笑,制造一个月全食,使每个城市的亮度变为零。Sunset可以执行以下操作任意次数:
·选择一个整数k(1≤k≤n)。
·选择k个不同的城市c1,c2,…,ck(1≤ci≤n),使它们相互连接。换言之,对于每一对不同选择的城市ci和cj(1≤i<j≤k),如果您在city ci,您可以到达city cj,而不必访问{c1,c2,…,ck}以外的城市。
·对于每个选定的城市ci(1≤i≤k),将bci降低1。
请注意,Sunset将始终选择具有最大可能值的k。现在Sunset想知道他需要做的最小操作数是多少,请编写一个程序来帮助他。
输入
输入的第一行包含单个整数T(1≤T≤10),即测试用例的数量。
对于每种情况,输入的第一行包含两个整数n和m(1≤n≤100000,1≤m≤200000),表示城市数量和道路数量。
输入的第二行包含n个整数b1,b2,…,bn(1≤bi≤109),表示每个城市的亮度。
以下每一条m线包含两个整数ui和vi(1≤ui,vi≤n,ui≠vi),表示第ui个城市和第vi个城市之间的一条双向道路。请注意,同一对城市之间可能有多条道路。
输出
对于每个测试用例,输出一行包含一个整数,即最小操作数。
题解:
每次一定是选择一个极大连通块,将里面所有数同时减小,直到最小值变成0,然后把变成0的点删掉,分裂成多个连通块继续。
为了实现这个策略,可以将整个过程倒过来看,先将所有点按照b的值从大到小排序,维护一个集合,依次将所有数加入到集合中。加入的时候遍历所有与x相令的点y,如果y在x之前加入且x和y不连通,则将x和y合并,并将y所在连通块的树根设为x。
每个x在成为最小值之前已经被做了b[pre[x]]次操作,所以每个点对答案的贡献是b[x]-b[pre[x]]
#include<bits/stdc++.h> using namespace std; const int maxn=2e5+100; typedef long long ll; vector<int> g[maxn]; int b[maxn]; int a[maxn]; int father[maxn]; int vis[maxn]; int findfather (int x) { int a=x; while (x!=father[x]) x=father[x]; while (a!=father[a]) { int z=a; a=father[a]; father[z]=x; } return x; } bool cmp (int x,int y) { return b[x]>b[y]; } int pre[maxn]; int t,n,m; int main () { scanf("%d",&t); while (t--) { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) { g[i].clear(); father[i]=i; pre[i]=0; vis[i]=0; scanf("%d",b+i); a[i]=i; } sort(a+1,a+n+1,cmp); for (int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); g[x].push_back(y); g[y].push_back(x); } ll ans=0; for (int i=1;i<=n;i++) { vis[a[i]]=1; for (int v:g[a[i]]) { if (!vis[v]) continue; int faV=findfather(v); if (faV==a[i]) continue; pre[faV]=father[faV]=a[i]; } } for (int i=1;i<=n;i++) ans+=b[i]-b[pre[i]]; printf("%lld\n",ans); } return 0; }