一、题目
二、解法
首先有一个朴素
d
p
dp
dp,因为每个数位都只会最多出现
1
1
1次,而且出现数位相同的不同情况最后也可以一起算答案(和一定),那么我们只需要统计出方案数,
d
p
[
i
]
dp[i]
dp[i]为二进制位出现的装压为
i
i
i,转移枚举包含
i
i
i的状态
j
j
j,设
a
[
i
]
a[i]
a[i]为值
i
i
i出现次数,
0
0
0需要单独考虑,转移为:
d
p
[
j
]
=
d
p
[
j
⊕
i
]
×
a
[
i
]
dp[j]=dp[j\oplus i]\times a[i]
dp[j]=dp[j⊕i]×a[i]这样做
O
(
min
(
a
,
n
)
a
)
O(\min(a,n)a)
O(min(a,n)a),优化
j
j
j的枚举方式,找到大于等于
i
i
i的全
1
1
1的数,那么我们可以拿到
i
i
i在这个集合下的补集,那么枚举这个补集的子集,就是
j
j
j比
i
i
i多出来的一部分,这样转移的复杂度是
O
(
3
k
)
O(3^k)
O(3k)(
2
k
>
a
2^k>a
2k>a)
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 300005;
const int jzm = 1e9+7;
int read()
{
int num=0,flag=1;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
return num*flag;
}
int n,m,k,mx,cnt,ans,a[M],b[M],dp[M],phi[M],p[M];
void sieve(int n)
{
phi[1]=1;
for(int i=2;i<=n;i++)
{
if(!phi[i])
{
p[++cnt]=i;
phi[i]=i-1;
}
for(int j=1;j<=cnt && i*p[j]<=n;j++)
{
if(i%p[j]==0)
{
phi[i*p[j]]=phi[i]*p[j];
break;
}
phi[i*p[j]]=phi[i]*phi[p[j]];
}
}
}
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
int t=read();
if(t==0) k++;
a[t]++;mx=max(mx,t);
}
m=1;
while(m<=mx)
m*=2;
sieve(m);
m--;dp[0]=1;
for(int i=1;i<=mx;i++)
{
b[i]=b[i>>1]<<1|1;
if(!a[i]) continue;
int tmp=b[i]^i;
for(int j=tmp;;j=(j-1)&tmp)
{
dp[j|i]=(dp[j|i]+1ll*dp[j]*a[i])%jzm;
if(!j) break;
}
}
for(int i=0;i<=m;i++)
ans=(ans+1ll*dp[i]*phi[1+i])%jzm;
for(int i=1;i<=k;i++)
ans=2ll*ans%jzm;
printf("%d\n",ans);
}