Description
你有一个双端队列和 (N) 个数字,先按 (1) 到 (N) 的顺序每次从任意一端插入当前数字,再进行 (N) 次操作每次可以从两端弹出,求有多少种弹出序列满足第 (K) 位为 (1)
(N le 2000)
Solution
考虑双端队列的样子,插入完成后,元素大小形象来看一定是一个"V"的形状,并且最低端是1。
再考虑符合要求的、合法的弹出序列的性质:
(1)首先第(K)个必须是1。
(2)前(K-1)个数,一定是两个或一个单调减的队列混合而成的。
(3)后(N-K)个数,其最大值应小于某一个(2)提到的单调队列的最小值。
一旦前(K-1)个数固定,最后剩下的就是一个单调的队列,取出方式有(2^{N-K-1})种。
所以接下来要算出合法序列的前(K-1)个数有多少种情况。
设(f_{i,j})表示已经确定了前(1...i)个数,且确定的数中最小值为(j),有多少种方案。
考虑从(f_{i,j})转移到(f_{i+1})。(f_{i,j})代表着若干种符合(j)这个特征的长度为(i)的数列,不论这些数列的两个(或者一个)单调队列是怎么构成的,我们只需要看看它们能够在第(i+1)位填上什么数合法转移就好。
首先,下一位填(1...j-1)都是可行的。由于当前序列是合法序列,也就是说满足(3)。可以这样拆分出两个队列,使得一个队列的最小值是(j),而另一个队列专门用来满足(3)。那么将新的数接在前面那个队列后面,仍然是合法序列。所以有(f_{i,j}
ightarrow f_{i+1,k};;;k<j)
其次,如果要填大于(j)的数呢?只能填没出现过的、最大的那个数。例如(n=7),当前序列是7 6 3 2,只能填入5。如果填的是其他数如4,你会发现,4一定要是某一个队列的结尾,由于它不是未出现的数的最大的数,这意味着后(N-K)个数的数列有比它更大的,那么这个队列不满足(3)。考虑另一个队列能否满足,事实上是不可能的,因为最小值一定要是另一个队列的结尾(不然就不止2个队列了),它也不满足(3)。
所以有(f_{i,j}
ightarrow f_{i+1,j})。这个转移有点神秘,它没有体现出任何(j)的变化,但它的确能表示,因为这一步转移相当于对每一个确切方案填了唯一确定的一个数,所以可以直接转移去对应特征的状态,也就是最小值仍然是(j)。
注意边界,那些(j>n-i+1)的(f_{i,j})是不合法的,那些(j=n-i+1)的状态不可以用于第二类转移,因为没有空余的数可以填。
第一个转移用后缀和优化,复杂度是(mathcal O(n^2))。
Code
#include <cstdio>
using namespace std;
const int N=2005,MOD=1e9+7;
int n,m;
int f[N];
void readData(){
scanf("%d%d",&n,&m);
}
void dp(){
f[n+1]=1;
int sum,last;
for(int i=1;i<m;i++){
sum=f[n-i+2];
for(int j=n-i+1;j>=2;j--){
(sum+=f[j])%=MOD;
if(j<=n-i+1)
f[j]=sum;
}
}
int ans=0;
for(int j=2;j<=n-(m-1)+1;j++) (ans+=f[j])%=MOD;
if(m==1) ans=1;
for(int i=1;i<=n-m-1;i++) (ans<<=1)%=MOD;
printf("%d
",ans);
}
int main(){
readData();
dp();
return 0;
}