【杭电多校2020】Total Eclipse【贪心】【并查集】

题意: n n n个点 m m m条边的无向图,每个点有一个正点权,每次选择一个连通子图,将里面的权值都减 1 1 1。求所有点权为 0 0 0的最小步数。

T ≤ 10 , n ≤ 1 0 5 , m ≤ 2 × 1 0 5 T\leq 10,n\leq 10^5,m\leq2\times10^5 T10,n105,m2×105

考虑一个贪心:每次一定选择一个极大的连通块。

感性理解很容易,还是证明一下:

假设一个极大连通块 S S S,我偏不选,选择它的子连通块来覆盖整个 S S S,答案严格更优。考虑两个连在一起的连通块 T 1 , T 2 T_1,T_2 T1,T2,选择 T 1 ∪ T 2 , T 1 ∩ T 2 T_1\cup T_2,T_1\cap T_2 T1T2,T1T2一定不比选 T 1 , T 2 T_1,T_2 T1,T2劣。因为选择的连通块覆盖了整个 S S S,所以可以一步步合并出 S S S(即任选一个与当前集合相邻的点,将覆盖它的集合与当前集合合并),答案不会更劣,矛盾。

对于一个连通块来说,一定是点按照权值从小到大被删。把操作顺序倒过来,就是把大的结点减小成和小的结点相同,然后一起删掉。

形式化地讲,就是把权值从大到小排序依次加入,并把全场的权值都减到当前权值。用并查集维护连通块个数即可。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

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

你可能感兴趣的:(【杭电多校2020】Total Eclipse【贪心】【并查集】)