原题链接
简洁题意
要求从 A A A选出子集 B B B使得 B B B中没有两个数按位与等于 0 0 0。每个子集 B B B的价值是集合内所有数的和加一的 ϕ \phi ϕ值。求所有子集 B B B的价值之和。
分析
可以思考,因为要知道所有的集合 B B B不容易,所以考虑计算价值相同的 B B B的个数。设 f i f_i fi表示所有值为 i i i的子集 B B B的数量,我们算出 f i f_i fi后乘上 p h i i + 1 phi_{i+1} phii+1就是这个方案的价值和,把每个价值和加在一起就是答案。考虑怎么计算这个 f f f,我们可以把 f f f拆成两部分,然后一部分直接用 f j f_j fj,另一部分用集合 A A A的一个数来补上就行。因为题目要 B B B中所有数的按位与均等于 0 0 0,所以不会出现有两个数的某一位都是 1 1 1的情况。所以我们在拆 i i i的时候可以把 i i i中为 1 1 1的位分成两半来计算。为了方便处理,我们把 n u m x num_x numx表示 a j a_j aj中值为 x x x的个数, s s s表示 i i i分出来的一个数, t t t为剩余的部分,为了保证不重复计算,要求 s > = t s>=t s>=t,根据乘法原理,则有状态转移方程 f i + = f s × n u m t f_i+=f_s\times num_t fi+=fs×numt。可是这样就有一个问题:这些数的和的上限很大,内存装不下啊!不用担心,因为我们选出来的数不会有两位都是 1 1 1的情况,所以选出来的和自然小于 2 ⌊ log 2 ( max { a i } ) + 1 ⌋ 2^{\lfloor\log_2(\max\{a_i\})+1\rfloor} 2⌊log2(max{ ai})+1⌋,以 2 ⌊ log 2 ( max { a i } ) + 1 ⌋ 2^{\lfloor\log_2(\max\{a_i\})+1\rfloor} 2⌊log2(max{ ai})+1⌋为上限即可。但是这样就会出现一个情况: a i = 0 a_i=0 ai=0算漏了!其实 a i a_i ai等于 0 0 0时用不用都没关系,根据乘法原理,所以答案要乘上 2 n u m 0 2^{num_0} 2num0。至于筛法求 p h i phi phi(就是在筛质数时筛 p h i phi phi函数)和求一个集合的子集(要拆 i i i),大家应该都会吧?
代码
#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;
}