思路:
先考虑如果序列的形态全部已知,如何在线性的时间内判断该序列能否合并超过 (2^k) 。
可以记录一个和 (sum) 表示当前可能继续合并的后缀的和,那么这个后缀一定是最长不上升的。
例如这个后缀:16 8 4 2 4
,最后一个 (4) 因为上升,无法合并到前面的序列中,与之前隔绝,所以不能加到前面的 (sum) 中。
那么分类讨论:
- 如果填的是 (2) ,由于 (2) 是最小的数,那么一定不会上升,直接将 (sum+=2) 即可。
- 如果填的是 (4) ,且 (sum) 中没有单独的 (2) ,即 (sum) 可以整除 (4),那么在这之前就没有小于 (4) 的数,可以直接将 (sum+=4) ( 即使有 (2) ,因为所有 (2) 都是成对的,所有都可以合并为 (4) ) 。
- 如果填的是 (4) ,且 (sum) 中有单独的 (2),即 (sum mod{4} = 2),那么就意味着出现了上述例子的情况,新增的 (4) 无法参与到之前的合并中,所以只能重新记录,即 (sum = 4)
当 (sum ge 2^k) 时,就意味着 (2^k) 可以被拼出来。
那么可以将这个过程写成 dp ,设 (dp[i][j]) 为填了前 (i) 个数,当前的 (sum) 为 (j) 时的方案数。
因为 (sum) 只要大于 (2^k),(2^k) 就可以合并出来,所以我们可以把 (sum ge 2^k) 时的答案也归入 (sum = 2^k) 中,即在 (2^k) 与更新完的 (sum) 中取 (min)。
那么可以得出转移方程:
当填的是 (2) 时:
- (dp[i][ min(j+2,2^k)] += dp[i-1]j])
当填的是 (4) ,且 (sum) 中没有单独的 (2) 时:
- (dp[i][min(j+4,2^k)] += dp[i-1]j])
当填的是 (4) ,且 (sum) 中有单独的 (2) 时:
- (dp[i][4] += dp[i-1]j])
当填的是 (0) 时:
- 既当成 (2) 转移一次,再当作 (4) 转移一次即可。
边界就是 (dp[0][0] = 1)
最后的答案就是 (dp[n][2^k])
Code:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<bitset>
#include<cmath>
#include<queue>
#include<map>
#include<set>
using namespace std;
int read()
{
int ans=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();}
return ans*f;
}
const int N=2005,K=11,mod=1e9+7;
int n,k,a,dp[N][(1<<K)+5];
int main()
{
n=read();k=read();
dp[0][0]=1;
// 边界
for(int i=1;i<=n;++i)
{
a=read();
for(int j=0;j<=(1<<k);++j)
// 枚举 sum
{
if(a==2||a==0)
// 当填的是 2 或 0 时
dp[i][min(j+2,1<<k)]=(dp[i][min(j+2,1<<k)]+dp[i-1][j])%mod;
if(a==4||a==0)
// 当填的是 4 或 0 时
{
if(j%4==2)
// 当 sum 中有单独的 2 时
dp[i][4]=(dp[i][4]+dp[i-1][j])%mod;
else
// 当 sum 中没有单独的 2 时
dp[i][min(j+4,1<<k)]=(dp[i][min(j+4,1<<k)]+dp[i-1][j])%mod;
}
}
}
printf("%d",dp[n][1<<k]);
return 0;
}