HDU 6763 Total Eclipse

Total Eclipse

题意
刚开始有一个联通的无向图,每个点都有自己的亮度,可以选择连通块,每次操作对连通块里面的点的亮度都进行减1,问最少要操作多少次才能都减为0.
(如果两点之间相隔着的点的亮度为0,视为这两点不连通)

思路
考虑暴力的做法,每次选择一个最大的连通块,然后所有点减去连通块中的点的亮度最小值,这是最优的贪心策略,但这样操作,显然会超时。
正难则反,那么我们考虑从所有点的亮度为0进行到当前所有点的亮度要怎么操作。每次都要删除一个连通块内的最小值,反过来操作,也就是每次选择亮度最大的点去加入。
然后继续加入点的时候,我们去看他的相邻点是不是已经加入,这样我们要计算他们的差值也就是操作次数,因为亮度大的比亮度小的多余的部分一定是只能选择自己进行加1(也就是正着操作的减1).
这里用并查集维护连通块,父节点为连通块中亮度最小的点
比如样例:
3 2
3 2 3
1 2
2 3
这是一条链,按照亮度排序后是3 3 2
我们把点1加入后,他相邻的点2,并没有加入,所以继续加入点3,同样相邻点2没有加入,继续操作。最后加入点2,和点2相邻的点1、点3都已加入,那么我们计算出他们的差值,sum+=3-2,sum+=3-2 然后把父节点更新为点2
最后操作完后,还需要加上每个点的父节点的亮度 这里就是sum+=2
故sum=4

#include 
using namespace std;
typedef long long ll;
const int N=1e5+50;
int a[N],b[N],f[N];
vector<int> e[N];
bool vis[N];
int find(int x){
  return f[x]==x?x:f[x]=find(f[x]);
}
int main() {
	ios::sync_with_stdio(0);
    int T;cin>>T;
    while(T--){
      int n,m;cin>>n>>m;
      for(int i=1;i<=n;i++) cin>>b[i],a[i]=f[i]=i,e[i].clear(),vis[i]=0;
	  for(int i=1;i<=m;i++){
		  int x,y;cin>>x>>y;
		  e[x].push_back(y);
		  e[y].push_back(x);
	  }
	  
	  sort(a+1,a+1+n,[](int x,int y){
		  return b[x]>b[y];
	  });
	  
	  ll sum=0;
	  for(int i=1;i<=n;i++){
		  int id=a[i];
		  vis[id]=1;
		  for(auto it:e[id]){
			  if(!vis[it]) continue;
			  int fx=find(id),fy=find(it);
			  if(fx!=fy) {
				  sum+=b[fy]-b[fx];
				  f[fy]=fx;
			  }
		  }
	  }
	  for(int i=1;i<=n;i++) if(f[i]==i)	sum+=b[i];
	  cout<<sum<<endl;
    }
    return 0;
}

你可能感兴趣的:(贪心,并查集,思维)