Description
给定一个长度为 n 的非负整数序列 A={a1,a2,...,an},对于 A 的一个子序列 B={ab1,ab2,...,abm}(1≤m≤n,1≤b1<b2<...<bm≤n,下同),称 B 是 A 的优秀子序列当且仅当,其任意两个不同元素的按位与结果均为 0,即:∀1≤i<j≤m,满足:abi and abj=0,其中 and 是按位与运算。
对于子序列 B={ab1,ab2,...,abm},我们定义其价值为 φ(1+abi),i∈[1,m],其中 φ(x) 表示小等于 x 的正整数中与 x 互质的数的个数。
现在请你求出 A 的所有优秀子序列的价值之和,答案对 109+7 取模。
Input
第一行一个正整数 n 表示序列长度。
第二行 n 个用空格分隔的非负整数,表示 a1,a2,...,an。
Output
仅一行一个整数,表示答案对 109+7 取模的结果。
Solution
分析
注意到子序列中任意两个不同的数按位与等于 0,每个二进制位最多在子序列中出现一次,说明相加不可能产生进位。
则一个子序列的和不会超过 218。记 d=log2 max{Ai}。
先用线性筛预处理 218 以内的欧拉函数。
积性函数:对于所有互质的整数 x 和 y,即 gcd(x,y)=1,有性质 f(xy)=f(x)f(y) 的数论函数。
欧拉函数是积性函数,即对于 gcd(x,y)=1,有性质 φ(xy)=φ(x)φ(y)。
对于一个素数 p,φ(p)=p-1,φ(pk)=pk-1(p-1)。
对于 10% 的数据,保证 n≤20
子序列的个数只有 2n 个,直接爆搜每个数选/不选。
(不知道筛到 218会不会全部TLE……没尝试过2333)
//10% #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e6+5,mod=1e9+7; int n,a[N],b[N],ans,p[N],phi[N],cnt; bool vis[N]; void dfs(int x){ if(x==n+1){ int res=0,k=0; bool flag=1; for(int i=1;i<=n;i++) if(vis[i]) b[++k]=a[i],res+=a[i]; for(int i=1;i<=k;i++){ for(int j=i+1;j<=k;j++) if((b[i]&b[j])!=0){flag=0;break;} if(!flag) break; } if(flag) ans=(ans+phi[res+1]%mod)%mod; return ; } vis[x]=1,dfs(x+1),vis[x]=0,dfs(x+1); } signed main(){ //freopen("sequence.in","r",stdin); //freopen("sequence.out","w",stdout); phi[1]=1; for(int i=2;i<=N;i++){ if(!p[i]) p[++cnt]=i,phi[i]=i-1; for(int j=1;j<=cnt&&i*p[j]<=N;j++){ p[i*p[j]]=1; if(i%p[j]==0){ phi[i*p[j]]=phi[i]*p[j]; break; } phi[i*p[j]]=phi[i]*(p[j]-1); } } scanf("%lld",&n); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); dfs(1),printf("%lld\n",ans); return 0; }
对于 10% 的数据,保证 ai≤1
记 cnts 表示数列中 Ai=s 的个数。
观察一下满足条件子序列的性质,可以得知至多选择一个 1(如果选 2 个按位与就不等于 0 了),而序列中的 0 可以任意选择。
可以先不考虑序列中的 0,最后的答案乘以 2cnt0。
只需考虑不选或选择一个 1。
//10% #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e6+5,mod=1e9+7; int n,a[N],cnt; int mul(int x,int n,int mod){ int ans=mod!=1; for(x%=mod;n;n>>=1,x=x*x%mod) if(n&1) ans=ans*x%mod; return ans; } signed main(){ //freopen("sequence.in","r",stdin); //freopen("sequence.out","w",stdout); scanf("%lld",&n); for(int i=1;i<=n;i++) scanf("%lld",&a[i]),cnt+=a[i]; printf("%lld\n",(cnt+1)*mul(2,n-cnt,mod)%mod); return 0; }
对于 30% 的数据,保证 ai≤1000
方法1:依然可以先不考虑序列中的 0。
注意到序列中有若干个数字 v,但是只能取至多 1 个,取的话则有 cntv 种选择。
从数值上来考虑。
设 fi,s 表示当前选了 i 个非 0 数,这些数的和(按位或)等于 s 的方案数。
以 i 为阶段转移,每次加入一个数 t,满足 s and t=0。
fi+1,s+t ← fi,s×cntt
由于一个长度为 i 的子序列会由于顺序原因被计算了 i! 次,则和为 s 的子序列的方案数 dps=fi,s/i!。
时间复杂度:O(4dd)。
方法2:从小到大枚举数字 t 加入,有 dps+t+=dps×cntt(s and t=0)。
直接暴力,时间复杂度:O(4d)。
对于 60% 的数据,保证 ai≤30000
考虑优化上述 30% 部分分的方法1,DP 转移时枚举的 t 可以看做是 s 的子集。
fi,s=fi-1,s-t×cntt,其中t⊂s。
用枚举子集的方法枚举 t 转移。
算法复杂度可优化至 O(3dd)。
对于 100% 的数据,保证 1≤n≤106,0≤ai≤2×105
由于上述做法要处理由于顺序原因带来的算重问题,所以要记一维DP状态 i。
按照特定顺序加入数字可以避免数据问题,比如按照最低位的大小顺序加入:
dps=dps-t×cntt
其中,t⊂s,lowbit(s)=lowbit(t)。
另外从大到小枚举 t 的方法直接改成枚举子集,也可以做到相同的复杂度。
统计答案即:ans=Σdpx×φ(1+x)×2cnt0。
时间复杂度:O(3d)。
代码鸽了