[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;
}
posted @ 2020-10-17 23:22  BILL666  阅读(89)  评论(0编辑  收藏  举报