【ARC068F】Solitaire 前綴和優化dp
阿新 • • 發佈:2018-08-08
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