[CF413D]2048
题目大意:
在一个长度为$n(n\le2000)$的数组中填数$2$或$4$,待所有数字全部填好后,按照类似于2048的规则向左合并。给定某些格子上的数,问在当前情况下要使得合并后的最大数超过$2^k$有几种填法。
思路:
动态规划。
定义一个状态为最长不上升后缀的数字和,如$(16,4,8,4,4,2)$对应的状态为$18$,因为后面这些还是有机会合并的,且合并的过程可以直接用加法代替,如$(16,4,8,4,4,2)$后面再加上一个$2$,对应的状态变为$18+2=20$。定义目标状态为$2^k$,超过这个的状态对其取$\min$。用$f[i][j]$表示前$i$个格子状态为$j$的方案数,则不难得到如下转移:
当$x=2$时,$f[i][\min(j+2,2^k)]+=f[i-1][j]$;
当$x=4$且当前最后有多余$2$时,新加进来的数不可能再和前面的合并了,故不将前面的计入状态,$f[i][4]+=f[i-1][j]$;
当$x=4$且当前最后无多余$2$时,$f[i][\min(j+4,2^k)]+=f[i-1][j]$。
当$x$不确定时,同时进行上述两种转移即可。
时间复杂度$O(n\cdot2^k)$。
1 #include<cstdio> 2 #include<cctype> 3 #include<algorithm> 4 inline int getint() { 5 register char ch; 6 while(!isdigit(ch=getchar())); 7 register int x=ch^'0'; 8 while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0'); 9 return x; 10 } 11 const int K=10,mod=1e9+7; 12 int f[2][(1<<K)+1]; 13 int main() { 14 const int n=getint(),k=getint()-1; 15 for(register int i=f[0][0]=1;i<=n;i++) { 16 const int x=getint(); 17 std::fill(&f[i&1][0],&f[i&1][1<<k]+1,0); 18 for(register int j=0;j<=1<<k;j++) { 19 if(x!=2) (f[i&1][j&1?2:std::min(j+2,1<<k)]+=f[(i&1)^1][j])%=mod; 20 if(x!=4) (f[i&1][std::min(j+1,1<<k)]+=f[(i&1)^1][j])%=mod; 21 } 22 } 23 printf("%d\n",f[n&1][1<<k]); 24 return 0; 25 }