1. 程式人生 > >【ARC068F】Solitaire 前綴和優化dp

【ARC068F】Solitaire 前綴和優化dp

tdi 大小 方案 inline \n 沒有 之前 不可 %d

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);
}

【ARC068F】Solitaire 前綴和優化dp