一、题目
二、解法
考虑 算法,我们不能去枚举每一条边,但是边权较小,我们可以从大到小枚举边权。设当前枚举到的边权为 ,我们定义 为二进制下包含子集 的权值,我们可以用 的时间把以前的 拿过来用(枚举 直到找到值),然后去枚举连的边,查找 是否存在,如果存在就合并查集,然后贡献答案,时间复杂度 。
注意一开始时直接贪心合并值相同的项,剩下来操作的都是值不同的,详见代码。
#include <cstdio>
#define int long long
const int N = 18;
const int M = 1<<N;
int read()
{
int x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,m,ans,a[M],p[M];
int findSet(int x)
{
if(x^p[x]) p[x]=findSet(p[x]);
return p[x];
}
int merge(int x,int y)
{
if(findSet(x)==findSet(y)) return 0;
p[findSet(x)]=findSet(y);
return 1;
}
signed main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
int x=read();
if(a[x]) ans+=x;
else a[x]=x;
}
for(int i=1;i<M;i++)
p[i]=i;
for(int i=M-1;i>=1;i--)
{
for(int j=0;j<N && (!a[i]);j++)
a[i]=a[i|(1<<j)];
for(int j=0;j<m;j++)
if(a[i|(1<<j)] && merge(a[i],a[i|(1<<j)]))
ans+=i;
}
printf("%lld\n",ans);
}