和風いろはちゃん / Iroha and Haiku's 题解

既然讲了,那就写下题解吧。

题目大意:
长度为 n n n 小标从 0 0 0 n − 1 n-1 n1 的序列 a a a,满足 1 < = a < = 10 1<=a<=10 1<=a<=10。如果存在 ( x , y , z , w ) ( 0 < = x < y < z < w < = n ) (x,y,z,w)(0<=x(x,y,z,w)(0<=x<y<z<w<=n) 满足:
a x + a x + 1 + . . . + a y − 1 = X a_x+a_{x+1}+...+a_{y-1}=X ax+ax+1+...+ay1=X
a y + a y + 1 + . . . + a z − 1 = Y a_y+a_{y+1}+...+a_{z-1}=Y ay+ay+1+...+az1=Y
a z + a z + 1 + . . . + a w − 1 = Z a_z+a_{z+1}+...+a_{w-1}=Z az+az+1+...+aw1=Z
那么说这个序列为 X − Y − Z X-Y-Z XYZ 序列
给出 n , X , Y , Z n,X,Y,Z n,X,Y,Z,求长度为 n n n X − Y − Z X-Y-Z XYZ 序列数,对 1 e 9 + 7 1e9+7 1e9+7 取模。
那不是枚举 x , y , z , w x,y,z,w x,y,z,w 然后再在开头结尾两段一个快速幂中间三段一个隔板法把总数乘起来就好了?
好,上代码:

#include 
using namespace std;
const int N=40+2,MOD=1e9+7;
long long n,xx,yy,zz,c[N][N],p[N],sum=0;
void init(){
	scanf("%lld%lld%lld%lld",&n,&xx,&yy,&zz);
}
void work(){
	for(int i=-1;++i<=40;c[i][0]=1);
	for(int i=0;++i<=40;) 
		for(int j=0;++j<=i;c[i][j]=(c[i-1][j]+c[i-1][j-1])%MOD);
	p[0]=1;
	for(int i=0;++i<=40;p[i]=p[i-1]*10%MOD);
	for(int x=-1;++x<=n;) for(int y=x;++y<=n;)
		for(int z=y;++z<=n;) for(int w=z;++w<=n;)
			(sum+=(p[x]*c[xx-1][y-x-1]%MOD*c[yy-1][z-y-1]%MOD
				*c[zz-1][w-z-1]%MOD*p[n-w]%MOD))%=MOD;
}
void prin(){
	printf("%lld",sum);
}
int main(){
	init();
	work();
	prin();
	return 0;
}

然后你去测下样例,你就发现你没了,为什么?
这种情况存在重复的序列,需要去重;然而即使到现在,我还是不会去重,根本想不出办法,所以换思路吧!
看到数据范围:
3 < = n < = 40 3<=n<=40 3<=n<=40
1 < = X < = 5 1<=X<=5 1<=X<=5
1 < = Y < = 7 1<=Y<=7 1<=Y<=7
1 < = Z < = 5 1<=Z<=5 1<=Z<=5
哦,数据范围这么小?
打表打表打表!!!
怎么可能,打表你要累死。。。
老实想状压dp吧。
考虑容斥,先求出总共的方案数,再求出不满足的方案数,减一下就行了。
开一个最多17位的状压状态,一个整数 s s s 1 1 1 后面跟 s − 1 s-1 s1 0 0 0 表示,即你在枚举的串的状态。
f [ i ] [ j ] f[i][j] f[i][j] 表示考虑到第 i i i 位时,它后面长度为 X + Y + Z X+Y+Z X+Y+Z 的长度的串为 j j j
那么我们只需要枚举当前位,再枚举后面的状态,再枚举一层当前位的状态加到总的状态中去,取我么需要的部分,看是否满足条件进行累加即可,最后再枚举不满足的情况减掉就行了。
附上为了注释稍微反压行了一下的压行代码:

#include 
using namespace std;
const long long N=40+2,MAX=(1<<17)+10,MOD=1e9+7;
long long n,x,y,z,f[N][MAX],sum=1,tot,cnt,ans;
//f[i][j]表示考虑到第i位时,后面串为j的状态 
void init(){
	scanf("%lld%lld%lld%lld",&n,&x,&y,&z);
}
void work(){
	for(int i=0;++i<=n;(sum*=10)%=MOD);//求出总共方案数 
	cnt=(1<<x+y+z)-1;//最多需要枚举的位数 
	tot=(1<<x-1)|(1<<x+y-1)|(1<<x+y+z-1);//满足情况(第x,x+y,x+y+z位为1) 
	f[0][0]=1;//初始状态 
	for(int i=0;++i<=n;) for(int j=-1;++j<=cnt;) for(int h=0;++h<=10;
	//i应该表示这题中表示数字的特殊方法的当前位,j为后面的状态,h为当前位状态 
		ans=(j<<h)|(1<<h-1),
	//状态上从后面拼上一个需要的数 
		ans&=cnt,
	//保持位数相等 
		(ans&tot)==tot?:(f[i][ans]+=f[i-1][j])%=MOD);
	//不满足的情况累加 
	for(int i=-1;++i<=cnt;((sum-=f[n][i])+=MOD)%=MOD);
}
void prin(){
	printf("%lld",sum);
}
int main(){
	init();
	work();
	prin();
	return 0;
}
感谢各位dalao捧场。

你可能感兴趣的:(题解)