CF1305G Kuroni and Antihype 题解

Codeforces
Luogu

Description.

\(n\) 个人,每个人有一个权值 \(a_i\)
\(i\) 个人和第 \(j\) 个人是朋友当且仅当:\(a_i\&a_j=0\)
有一个传销组织,每个人可以通过以下两种方式加入它

  1. 自愿加入,不会获得任何价值
  2. 一个人可以邀请他的好友加入,他能获得他权值的价值

现在这 \(n\) 个人想去恰烂钱,就决定去参加这个传销组织。
问他们最多能从传销组织那里骗来多少钱。
到底谁是传销组织啊 /fad

Solution.

*3500 就这
题目看错了,看成一个人邀请好友加入能获得好友权值的价值了。

选两个人,合并,能获得 \(a_i\) 的价值,最后点权变成了 \(a_j\)
考虑现在图应该是一个森林,我们考虑加入一个 \(0\) 点并钦定它第一个参加传销组织。
那这样必定答案不变,且不再有点需要自愿参加传销组织。

然后,我们考虑如果 \(x\) 邀请 \(y\) 参加,那就从 \(x\)\(y\) 连一条边。
这样,我们连出来了一棵树,我们发现树的权值是每个点的点权乘上它儿子个数。
我们可以考虑把每个点的权值下放,下放到到孩子的边上去。
那这样每条边的边权就是它父亲那边节点的权值。
可惜,现在是有根树,而连边关系是无向的,我们能否转化成无向关系呢?
能,考虑把每个节点的权值加到它到父亲的权值,这样除了根 \(0\) 之外全都被多算了一次,全减掉即可。

那我们抽象成了这样一个问题,每两个点之间的边权是它们之和,有边当且仅当权值 \(\&\) 和为 \(0\)
然后我们需要求一棵最小生成树。

因为 \(x\&y=0\),则 \(x+y=x\oplus y=x|y\),所以我们可以枚举边权,然后 \(x\) 必定是边权的子集。
所以我们可以在 \(O(3^n)\) 枚举出两个点,然后,我们用 DSU 合并即可。

Coding.

点击查看敲短代码
//是啊,你就是那只鬼了,所以被你碰到以后,就轮到我变成鬼了{{{
#include<bits/stdc++.h>
using namespace std;typedef long long ll;
template<typename T>inline void read(T &x)
{
	x=0;char c=getchar(),bz=0;
	for(;c<48||c>57;c=getchar()) if(!(c^45)) bz=1;
	for(;c>=48&&c<=57;c=getchar()) x=(x<<1)+(x<<3)+(c^48);
	bz?x=-x:x;
}/*}}}*/
const int N=262144;int n,fa[N],ct[N];
inline int getf(int x) {return fa[x]==x?x:fa[x]=getf(fa[x]);}
inline int mrg(int x,int y)
{
	x=getf(x),y=getf(y);if(x==y) return 0;
	int rs=ct[x]+ct[y]-1;return fa[x]=y,ct[y]=1,rs;
}
int main()
{
	read(n),ct[0]=1;ll rs=0;for(int i=0;i<N;i++) fa[i]=i;
	for(int i=1,x;i<=n;i++) read(x),ct[x]++,rs-=x;
	for(int S=N-1;~S;S--) for(int T=S;T;(--T)&=S)
		if(ct[T]&&ct[S^T]) rs+=1ll*S*mrg(T,S^T);
	return printf("%lld\n",rs),0;
}
posted @ 2021-08-10 18:31  Peal_Frog  阅读(44)  评论(0编辑  收藏  举报