题目大意:问有多少长度为N的数列{aN},满足每个数字权值在[1,10]中,并且不存在1<=x< y< z< w<=N, 使得S[x,y)=X,S[y,z)=Y, S[z,w)=Z。其中X, Y, Z是给定的正整数,S[x,y)表示a[x]+a[x+1]+…+a[y-1]。
N<=40, X<=5, Y<=7, Z<=5 。
题解:如果n小一点就可以容斥了;可以直接dp,但是会有重复计数,而且很难去重,于是显然去考虑问题的反面,即有多少序列不包含(X,Y,Z)?
我们把问题做这样一个转化, 即把数字i看成一个二进制数,其中第i位是1。例如1看成1,2看成10,3看成100,5卡成10000.这样有什么好处呢?好处在于,例如,(2,3)会被看作10100,而5被看作10000,10100包含了10000.可以推广证明,如果一列数的末尾是(X,Y,Z)的话,那么看成二进制后就会包含(X,Y,Z)转化的二进制数。这样设dp[i][j]表示前i个数字看成的二进制数字的最后17位(因为x+y+z<=17)是j的方案数,最后从总数10^n中减去不合法的即可。
代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#define lint long long
#define LOG 17
#define maxK 10
#define V 1<<LOG
#define N 42
#define mod 1000000007
using namespace std;
inline int updv(int x,int p) { return x|(1<<(p-1)); }
int dp[N][V],can[V];
int fast_pow(int x,int k)
{
int ans=1;
while(k)
{
if(k&1) ans=(lint)ans*x%mod;
x=(lint)x*x%mod,k>>=1;
}
return ans;
}
int show(int x)
{
for(int i=1;i<=LOG;i++)
printf("%d",(x&(1<<(i-1)))>>(i-1));
cout<<endl;
return 0;
}
int main()
{
int n,x,y,z;scanf("%d%d%d%d",&n,&x,&y,&z);
int all=(1<<LOG)-1,v=0,ans=0;dp[0][0]=1;
v=updv(v,x+y+z),v=updv(v,y+z),v=updv(v,z);
for(int i=0;i<=all;i++) can[i]=((i&v)!=v);
for(int i=0,x;i<n;i++)
for(int j=0;j<=all;j++)
if(can[j]&&(x=dp[i][j]))
for(int k=1;k<=maxK;k++)
(dp[i+1][all&updv(j<<k,k)]+=x)%=mod;
for(int i=1;i<=all;i++)
if(can[i]) (ans+=dp[n][i])%=mod;
printf("%d\n",(fast_pow(10,n)-ans+mod)%mod);
return 0;
}