LG P6570 [NOI Online #3 提高组]优秀子序列
Description
给定一个长度为 $n$ 的非负整数序列 $A=\{a_1,a_2,\cdots,a_n\}$,对于 $A$ 的一个子序列 $B=\{a_{b_1},a_{b_2},\cdots,a_{b_m}\}$($0\le m\le n$,$1\le b_1 < b_2 < \cdots < b_m\le n$,下同),称 $B$ 是 $A$ 的优秀子序列当且仅当,其任意两个不同元素的按位与结果均为 $0$,即:$\forall 1\le i < j\le m$,满足:$a_{b_i}~\mathrm{and}~a_{b_j}=0$,其中 $~\mathrm{and}~$ 是按位与运算。
对于子序列 $B=\{a_{b_1},a_{b_2},\cdots,a_{b_m}\}$,我们定义其价值为 $\varphi(1+\sum_{i=1}^m a_{b_i})$,其中 $\varphi(x)$ 表示小等于 $x$ 的正整数中与 $x$ 互质的数的个数。
现在请你求出 $A$ 的所有优秀子序列的价值之和,答案对 $10^9+7$ 取模。
Solution
如果将每个数视为$2$的幂次的集合,那么题目就是要求$\sum_{x \in S}x$,$S$是一个合法序列
因为$2$的幂次相加不会进位,上式就是$\cup_{x \in S}x$
设$dp_i$表示合法序列中所有元素并为$i$的方案数
那么题中所求即为
$$\sum dp_x \varphi(x+1)$$
$\varphi$可以一次线性筛求出,$dp$可以一次子集DP求出
最后加上空集
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> #include<cmath> using namespace std; long long n,maxx,tot,m; long long dp[400005]={1},phi[400005],buc[400005],prime[400005],a[1000005],ans=1; bool vst[400005]; const long long mod=1e9+7; inline long long read() { long long f=1,w=0; char ch=0; while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9') w=(w<<1)+(w<<3)+ch-'0',ch=getchar(); return f*w; } void pre(long long N) { phi[1]=1; for(long long i=2;i<=N;i++) { if(!vst[i]) prime[++tot]=i,phi[i]=i-1; for(long long j=1;j<=tot&&i*prime[j]<=N;j++) { vst[i*prime[j]]=true; if(!(i%prime[j])) { phi[i*prime[j]]=phi[i]*prime[j];break; } else phi[i*prime[j]]=phi[i]*phi[prime[j]]; } } } int main() { n=read(); for(long long i=1;i<=n;i++) a[i]=read(),maxx=max(maxx,a[i]),buc[a[i]]++; while((1<<m)<=maxx) m++; pre(1<<m); for(long long i=1;i<=(1<<m);i++) { for(long long s=i;;s=(s-1)&i) { long long t=i^s; if(s<t) break; (dp[i]+=dp[t]*buc[s]%mod)%=mod; } (ans+=dp[i]*phi[i+1]%mod)%=mod; } for(long long i=1;i<=buc[0];i++) (ans*=2)%=mod; printf("%lld\n",ans); return 0; }