【CTSC2017】吉夫特(状压dp)

一句话描述:给你一个包含N个元素的序列,求有多少个长度k>=2的子序列满足:C_{a_1}^{a_2}*C_{a_2}^{a_3}*...*C_{a_{k-1}}^{a_k}\ mod\ 2 =1  。

分析

一看到CTSC就吓住了。然而自己稍微分析了下感觉有点思路,又上洛谷看了题解,(看了代码后)感觉没有想象中的难。

其实是要求每一项组合数mod2都不能为0,也就是每一项都必须为奇数。百度之后可以知道,若C_n^m为奇数,那么有(n&m)=m。也就是说如果用状压的思想将n,m表示为二进制数,那么m一定是n的子集。

进一步思考,枚举每一个数的子集并不需要多少时间,关键在于如何快速确定子集即这个数的位置。设p[i]表示数字i在序列中的位置,f[i]表示以数字i结尾的序列的答案。那么状态转移方程为:f(S)=\sum_{k\epsilon S,P[k]<P[S]}f(k)。意思是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;
}

猜你喜欢

转载自blog.csdn.net/WWWengine/article/details/81287414
今日推荐