【状压】ARC058E 和風いろはちゃん / Iroha and Haiku
是我想不到的的状压方式。
首先,条件中的“存在”比较麻烦,由于一个相同的数列可能包含多个满足条件的三元组,用乘法原理直接正面刚会出现重复计数的情况。
正难则反,所以我们反向考虑用总的\(10^n\)的方案数减去不合法的数列。
注意到\(X+Y+Z\leq 17\),所以可以考虑状压。
状压的方式非常不容易想到,具体是:
我们用二进制位数来表示数字,例如:\(5->10000 \ ,\ 3->100\)
如果\(5\)后面是加上一个\(3\),即序列的一个后缀是\(53\),那么这个状态就用\(10000100\)表示(把两个数的二进制表示拼在一起)。同时,这个新的二进制状态的第\(8\)位是\(1\),表示\(5+3=8\);第\(3\)位为\(1\),表示最后一个数是\(3\),那么倒数第二个数就可以用\(8-3=5\)算出来。
举个更复杂的例子,假如后缀是\(536\)(五三打钱),我们的二进制状态就是\(10000100100000\),那么我们可以通过这个二进制状态得到所有\(536\)这个后缀的所有情况:
\(14\)(第\(14\)位上为\(1,14=5+3+6\))
\(9\)(第\(9\)位上为\(1,9=3+6\))
\(6\)(第\(6\)位上为\(1,6=6\))
\(5\)(\(14-9=5=5\))
\(8\)(\(14-6=8=5+3\))
\(3\)(\(9-6=3=3\))
(这实际上就是一种变相的后缀和)
那么什么样的状态表示俳句呢?
俳句是连续三段数,和分别为\(X,Y,Z\),所以俳句对应的二进制第\(X+Y+Z,Y+Z,Z\)位上都是\(1\)。
用这种方式表示状态,状态的最大位数就是最大和,所以状态数是\(2^{X+Y+Z}\)种,在时空承受范围之内。
关于\(dp\)过程,设\(dp[i][s]\)表示转移到第\(i\)位,数列结尾的状态为\(s\)的方案数。
转移时枚举后继数字(第\(i+1\)位)为\(j\),那么新的二进制状态就是\(s'=(s<<j)|(1<<(j-1))\)
则有\(dp[i+1][s']+=dp[i][s]\)
边界情况\(dp[0][0]=1\)
另外,这里有一点需要提一下,就是\(n\)的范围比较大,而每个数字的范围在\([1,10]\),那么最大的数字和应该是\(40\),但是我们只需要\(dp\)和是\(x+y+z\)的部分,因为这个是后缀和,前面的数不管是啥并不重要,我们的\(i\)指针在不断移动的时候,就计算了不同位置的方案。
►Code View
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<string>
#include<map>
#include<cmath>
using namespace std;
#define N 45
#define INF 0x3f3f3f3f
#define MOD 1000000007
#define LL long long
int rd()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48); c=getchar();}
return f*x;
}
int n,x,y,z;
LL dp[N][1<<18];
LL ksm(LL a,LL b)
{
LL res=1;
while(b)
{
if(b&1) res=res*a%MOD;
a=a*a%MOD;
b>>=1;
}
return res;
}
int main()
{
n=rd(),x=rd(),y=rd(),z=rd();
int jdg=((1<<(x+y+z-1))|(1<<(y+z-1))|(1<<(z-1)));
dp[0][0]=1;
int sum=(1<<(x+y+z))-1;//最高位是x+y+z-1 所以最大和就是所有位数都是1 也就是2^(x+y+z)-1
for(int i=0;i<=n-1;i++)
for(int s=0;s<=sum;s++)
{
if(dp[i][s]==0) continue;//不会产生贡献
for(int j=1;j<=10;j++)//枚举第i+1位是什么数字
{
int t=(s<<j)|(1<<(j-1));
t&=sum;//防止溢出
if((t&jdg)==jdg) continue;//存在俳句
else dp[i+1][t]=(dp[i+1][t]+dp[i][s])%MOD;
}
}
LL ans=0;
for(int s=0;s<=sum;s++)
ans=(ans+dp[n][s])%MOD;
ans=(ksm(10,n)-ans+MOD)%MOD;
printf("%lld\n",ans);
return 0;
}