CodeForces 1305G Kuroni and Antihype
https://codeforces.com/contest/1305/problem/G
有一个组织,它的奖励机制如下.
- 如果一个人加入了组织,他的奖励为 (0)
- 如果一个人邀请了他的朋友加入了组织,他的奖励为他自己的年龄.
有 (n) 个人想要通过合作加入这个组织使得他们的奖励之和最大,其中第 (i) 个人的年龄为 (a_i) .特别的, (i,j) 两人为朋友当且仅当 (a_i AND a_j=0) .
问最大奖励之和.
(1 le n le 2 imes 10^5)
(0 le a_i le 2 imes 10^5)
Tutorial
首先假设有一个 (a_0=0) 的人已经在组织内,那么就仅有邀请加入这一种方式.
假设所有朋友关系的人之间有一条边,对于一条边 ((u,v)) ,如果 (u) 邀请了 (v) 或 (v) 邀请了 (u) ,则令这条边为红色,发现所有红色的边形成了一个树的结构.
设 (a_0=0) 为这棵树的根,则每个点 (i) 的贡献为 (a_i) 乘上儿子数.考虑令每条边 ((u,v)) 的边权为 (a_u+a_v) ,发现答案就是边权和减去所有 (a_i) 的和.
所以现在将其转化为最大生成树的问题,可以采用Kruskal的思路,枚举 (s=2^{18}-1,2^{18}-2,cdots,0) .和 (t in s) ,加入 (t) 和 (s XOR t) 的连边,它们的边权为 (s) .
复杂度 (O(3^{18}))
Code
https://codeforces.com/contest/1305/submission/72366838
#include <cstdio>
#define debug(...) fprintf(stderr,__VA_ARGS__)
using namespace std;
typedef long long ll;
const int maxn=1<<18;
int n;
int cnt[maxn];
bool vis[maxn];
namespace us
{
int fa[maxn];
void init(int n)
{
for(int i=0;i<=n;++i) fa[i]=i;
}
int find(int a) {return a==fa[a]?a:fa[a]=find(fa[a]);}
int merge(int a,int b)
{
a=find(a),b=find(b); if(a==b) return 0;
int re=-1;
re+=vis[a]?1:cnt[a]; vis[a]=1;
re+=vis[b]?1:cnt[b]; vis[b]=1;
fa[a]=b;
return re;
}
}
int main()
{
scanf("%d",&n);
ll an=0;
for(int i=1;i<=n;++i)
{
int x; scanf("%d",&x);
++cnt[x];
an-=x;
}
++cnt[0];
us::init((1<<18)-1);
for(int s=(1<<18)-1;~s;--s)
{
for(int t=s;;t=(t-1)&s)
{
if(cnt[t]&&cnt[s^t])
{
an+=(ll)s*us::merge(t,s^t);
}
if(t==0) break;
}
}
printf("%I64d
",an);
return 0;
}