思路:

先考虑如果序列的形态全部已知,如何在线性的时间内判断该序列能否合并超过 \(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 \bmod{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;
}