【AtCoder Regular Contest 058 E】Iroha and Haiku
状压DP。
题目大意:
给定 $n,X,Y,Z$,问有多少长度为 $n$ 的数列,满足:
- 数列中都是 $1\sim 10$ 的整数。
- 数列中存在一个连续子序列,这个子序列可以分成三个连续的部分,使得三部分的和依次是 $X,Y,Z$。
题解:
由于 $X,Y,Z$ 和 $X+Y+Z$ 都比较小,我们考虑状压。
对于 $x$,我们用二进制数 $2^{x-1}$ 表示(即 $1$ 后面跟 $x-1$ 个 $0$)。这样每个状态,我们都可以用若干个这样的数首尾相连得到。
不难发现,一个 $1$ 到它后面的某个 $1$ 之前(或到末尾)有多少位,就代表这中间的和是多少。有多少个 $1$ 就代表有多少个数。
那么,如果一个状态中第 $X+Y+Z$ 位、第 $Y+Z$ 位、第 $Z$ 位都是 $1$,则说明构成满足题目条件的子序列。
在后面加数,直接插入到尾部即可。开头超过 $X+Y+Z$ 的部分会自动溢出。
用状压 DP 计算不存在这样的段的情况。转移直接枚举插进去的数即可。
时间复杂度 $O(2^{X+Y+Z}n)$。
Code:
#include<cstdio>
const int md=1e9+7;
int n,x,y,z,dp[41][1<<18],ans=1;
inline void upd(int&a){a+=a>>31&md;}
int main(){
scanf("%d%d%d%d",&n,&x,&y,&z);
const int C=1<<(x+y+z-1)|(1<<(y+z-1))|(1<<z-1),ALL=(1<<x+y+z)-1;
dp[0][0]=1;
for(int i=1;i<=n;++i){
ans=ans*10LL%md;
for(int S=0;S<=ALL;++S)if(int k=dp[i-1][S])if((S&C)!=C)
for(int w=1;w<=10;++w)upd(dp[i][(S<<w|(1<<(w-1)))&ALL]+=k-md);
}
for(int S=0;S<=ALL;++S)
if((S&C)!=C)upd(ans-=dp[n][S]);
printf("%d\n",ans);
return 0;
}