2022.7.4 CF1305G

题意:给定一张无向图 \(G\),点有点权 \(a_i\),其中 \(i\)\(j\) 有边当且仅当 \(i\and j=0\)。你需要先选一个点 \(s\) 进入集合 \(S\),然后操作 \(n-1\) 次使得所有点都进入 \(S\) 集合:选定 \((u,v),u\in S,v\notin S\),获得 \(a_u\) 的收益并且将 \(v\) 加入 \(S\)。你需要使总收益最大。

瞎扯:可以转化为求 \((i,j)\) 的权为 \(a_i+a_j\),答案就是 \(\text{最大生成树}-\sum\limits_{i=1}^n a_i+\max a_i\),发现可能成为森林,所以我们加一个超级根,使其权为 \(0\) 就巧妙地解决了这一问题。

正解:想不到吧!直接暴力 kruskal 就过了,只是要用桶排去掉那个 \(\log\)。讲一下细节,对于权值 \(x,y\),没有入度的节点个数分别为 \(cnt_x,cnt_y\),那么一次操作就是可以分选一个 \(x,y\),连向所有点,然后再随意连一条边,这样就是树了,所以贡献为 \((cnt_x+cnt_y-1)*(x+y)\),然后要将 \(cnt_x,cnt_y\) 都置为 \(1\),这是因为这两个中只会用到一个,若两个都用到则一定成环了(易证)。有时间再补 boruvka 的做法。

#include<bits/stdc++.h>
using namespace std;
#define inf 1e9
const int all=1<<18;
const int maxn=1e6+10;
const int mod=1e9+7;
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
long long ans;
int n,m,a[maxn],f[maxn],cnt[maxn];
inline int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
int main(){
	n=read();cnt[0]=1;
	for(int i=0;i<all;i++)f[i]=i;
	for(int i=1,x;i<=n;i++)
		x=read(),ans-=x,cnt[x]++;
	for(int S=all-1;~S;--S)
		for(int x=S,y;;x=(x-1)&S){
			y=S^x;
			if(cnt[x]&&cnt[y]&&find(x)!=find(y)){
				ans+=1ll*(cnt[x]+cnt[y]-1)*S;
				cnt[x]=cnt[y]=1;f[find(x)]=find(y);
			}if(!x)break;
		}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2022-07-05 18:13  syzf2222  阅读(12)  评论(0编辑  收藏  举报