【ARC068F】Solitaire 前缀和优化dp

Description

​ 你有一个双端队列和 N 个数字,先按 1到 N 的顺序每次从任意一端插入当前数字,再进行 N 次操作每次可以从两端弹出,求有多少种弹出序列满足第 K 位为 1。

Input

​ 一行两个整数 N 和 K。

Output

​ 一个整数表示答案,对 10^9+7取模。

Sample Input

Sample #1
2 1

Sample #2
17 2

Sample #3
2000 1000

Sample Output

Sample #1
1

Sample #2
262144

Sample #3
674286644

HINT

1≤K≤N≤2000

Sol

显然我们发现这个生成的序列一定是一个V字形,1是最底端。

然后这个删除序列一定有这样一个性质:构造删除序列的时候,新加入的数字要么是未出现过的数字的最大值,要么严格小于出现过的数字的最小值。

证明:我们假设当前最小值是从左边取出来的,那么从左边取一定是严格小于它的,从右边取的话,你不可能取出小于未出现过的数字的最大值的数,因为这个未出现的最大值还没有被取出来,比他小的自然也取不出来。而如果从最小值到最大值都取了的话,你下一步取出的一定是新的最小值。

所以我们设\(f[i][j]\)表示删除序列大小为i,当前最小值为j的方案数,那么\(f[i][j]=\sum_{k=j}^{n}f[i-1][k]\),也就是要么原来的最小值就是j,这次取出来了一个未出现的最大值,要么这次取的数是j。

用前缀和优化可以做到\(O(n^2)\)

但是有个问题,1可能在k位之前就出现了,所以\(f[k][1]\)不是答案,答案是\(f[k][1]-f[k-1][1]\),这样就减去了1提前出现的情况,之后后面的序列方案数就是\(2^{n-k-1}\),即:枚举它是从左边还是右边出来的。

Code

#include <cstdio>
int n,f[2005][2005],s[2005],k,P=1e9+7,a;
int main()
{
    scanf("%d%d",&n,&k);f[0][n+1]=1;
    for(int i=1;i<=k;i++) for(int j=n+1;j;j--) s[j]=(s[j+1]+f[i-1][j])%P,f[i][j]=(j<=n-i+1?s[j]:0);
    a=(f[k][1]-f[k-1][1]+P)%P;
    for(int i=1;i<=n-k-1;i++) a=(a+a)%P;
    printf("%d\n",a);
}

猜你喜欢

转载自www.cnblogs.com/CK6100LGEV2/p/9445414.html