[NOI Online #3 提高组]优秀子序列
desciption
solution:
这道题得一步一步来
直接求答案肯定不好求,思考怎样将答案进行分类
观察到答案奇怪的形式,显然是让我们根据优秀子序列的和来分类
于是我们记\(f_{s}\)表示优秀子序列和为s时有多少中方案
那么最终答案就是\(ans=\sum_sf_s*\phi (s+1)\)
然后我们发现这个看似定义得很随意的状态竟然和题目惊人地契合
考虑增量地构造一个优秀子序列,设其和为s(显然一个本就优秀的子序列去掉其中任何一个数后仍然是优秀的)
设将要加入的数为p
p能够加入当且仅当\(p\)&\(s==0\)
而如果数p加入后,\(f_s\)就恰好能对\(f_{s+p}\)产生一个新的贡献
那么状态转移方程就不难想到了:\(f_s=\sum f_{s-t}*cnt_t\)
其中\(cnt_t\)为t在原序列中出现的次数,\(t\)&\(s==0\)
边界情况是\(f_0=2^{cnt_0}\)
然后就可以dp了
需要注意的是如果直接dp可能会重复(比如(1,2)和(2,1)会被计算两次)
于是我们可以从小到大地遍历序列中的数,然后对于每一个数统计它对答案的贡献
code:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,M=(1<<18)+5,mod=1e9+7;
int cnt[N],f[M],n,w;
inline int read()
{
int s=0,w=1; char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
return s*w;
}
int phi[M];
vector<int>p;
bool flag[M];
inline void pre(int lim)
{
phi[1]=1;flag[1]=0;
for(int i=2;i<=lim;++i)
{
if(!flag[i])p.push_back(i),phi[i]=i-1;
for(int j=0;j<p.size();++j)
{
if(i*p[j]>lim)break;
flag[i*p[j]]=true;
if(i%p[j])phi[i*p[j]]=phi[i]*phi[p[j]];
else{phi[i*p[j]]=phi[i]*p[j];break;}
}
}
}
inline int qpow(int x,int y)
{
int ans=1;
for(;y;y>>=1,x=1ll*x*x%mod)
if(y&1)ans=1ll*ans*x%mod;
return ans;
}
int main()
{
n=read();
int mx=0;
for(int i=1;i<=n;++i)
{
int x=read();++cnt[x];
mx=max(mx,x);
}
w=(int)log2(mx)+1;
pre(1<<w);f[0]=qpow(2,cnt[0]);
int p=0;
for(int i=1;i<=mx;++i)
if(cnt[i])
{
p|=i;int s=p^i;
for(int t=s;;t=(t-1)&s)
{
f[t|i]=(f[t|i]+1ll*f[t]*cnt[i]%mod)%mod;
if(!t)break;
}
}
int ans=0;
for(int i=0;i<(1<<w);++i)
ans=(ans+1ll*f[i]*phi[i+1]%mod)%mod;
cout<<ans;
return 0;
}
NO PAIN NO GAIN