一句话描述:给你一个包含N个元素的序列,求有多少个长度k>=2的子序列满足: 。
分析
一看到CTSC就吓住了。然而自己稍微分析了下感觉有点思路,又上洛谷看了题解,(看了代码后)感觉没有想象中的难。
其实是要求每一项组合数mod2都不能为0,也就是每一项都必须为奇数。百度之后可以知道,若为奇数,那么有(n&m)=m。也就是说如果用状压的思想将n,m表示为二进制数,那么m一定是n的子集。
进一步思考,枚举每一个数的子集并不需要多少时间,关键在于如何快速确定子集即这个数的位置。设p[i]表示数字i在序列中的位置,f[i]表示以数字i结尾的序列的答案。那么状态转移方程为:。意思是k是二进制数S的子集,比S小,且k的位置在S之后。
为了方便,把长度为1的也考虑进去,只需在统计答案时减去就行。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=211990;
const int MAXM=233335;
const int mod=1000000007;
int A[MAXN],f[MAXM],p[MAXM];
char c;
void scan(int &x)
{
for(c=getchar();c<'0'||c>'9';c=getchar());
for(x=0;c>='0'&&c<='9';c=getchar()) x=x*10+c-'0';
}
int main()
{
int N,i,j;
scan(N);
for(i=1;i<=N;i++)
{
scan(A[i]);
p[A[i]]=i; //位置
f[A[i]]=1; //初始化
}
for(i=1;i<=N;i++)
for(j=(A[i]-1)&A[i];j;j=(j-1)&A[i]) //枚举A[i]的子集
if(p[j]>i) //如果在A[i]的后面
f[j]=(f[j]+f[A[i]])%mod;
int ans=0;
for(i=1;i<=N;i++) ans=(ans+f[A[i]]-1)%mod;
cout<<ans;
return 0;
}