简洁题意
要求从 A 选出子集 B 使得 B 中没有两个数按位与等于 0。每个子集 B 的价值是集合内所有数的和加一的 φ 值。求所有子集 B 的价值之和。
分析
可以思考,因为要知道所有的集合 B 不容易,所以考虑计算价值相同的 B 的个数。
设 f[i] 表示所有值为 i 的子集 B 的数量,我们算出 f[i] 后乘上 φ[i+1] 就是这个方案的价值和,把每个价值和加在一起就是答案。
考虑怎么计算这个 f ,我们可以把 f 拆成两部分,然后一部分直接用 f[j] ,另一部分用集合 A 的一个数来补上就行。因为题目要 B 中所有数的按位与均等于 0 ,所以不会出现有两个数的某一位都是 1 的情况。所以我们在拆 i 的时候可以把 i 中为 1 的位分成两半来计算。为了方便处理,我们把 num[x] 表示 a[j] 中值为 x 的个数, s 表示 i 分出来的一个数, t 为剩余的部分,为了保证不重复计算,要求 s≥ts ,根据乘法原理,则有状态转移方程 。
啊啊啊!没LaTeX好难受!!!!!
可是这样就有一个问题:这些数的和的上限很大,内存装不下啊!不用担心,因为我们选出来的数不会有两位都是1的情况,所以选出来的和自然小于 ,以 为上限即可。
但是这样就会出现一个情况:a[i]=0 算漏了!其实 a[i] 等于 0 时用不用都没关系,根据乘法原理,所以答案要乘上 2^{num0} 。
最后奉上 AC Code
#include<bits/stdc++.h>
using namespace std;
const int NN=300000,P=1e9+7;
int num[NN],phi[NN],f[NN];
bool vis[NN];
int main()
{
int n,maxx=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
num[x]++;
maxx=max(maxx,x);
}
int m=0;
while((1<<m)<=maxx)
m++;
phi[1]=1;
for(int i=2;i<=(1<<m);i++)
if(!vis[i])
for(int j=i;j<=(1<<m);j+=i)
{
vis[j]=true;
if(!phi[j])
phi[j]=j;
phi[j]=phi[j]/i*(i-1);
}
f[0]=1;
for(int i=1;i<=(1<<m);i++)
for(int s=i;;s=(s-1)&i)
{
int t=i^s;
if(s<t)
break;
f[i]=(f[i]+1ll*f[t]*num[s])%P;
}
int ans=0;
for(int i=0;i<=(1<<m);i++)
ans=(ans+1ll*f[i]*phi[i+1])%P;
for(int i=1;i<=num[0];i++)
ans=ans*2ll%P;
printf("%d",ans);
return 0;
}