【ARC068F】Solitaire

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}\rightarrow f_{i+1,k}\;\;\;k<j\)
  
​   其次,如果要填大于\(j\)的数呢?只能填没出现过的、最大的那个数。例如\(n=7\),当前序列是7 6 3 2,只能填入5。如果填的是其他数如4,你会发现,4一定要是某一个队列的结尾,由于它不是未出现的数的最大的数,这意味着后\(N-K\)个数的数列有比它更大的,那么这个队列不满足(3)。考虑另一个队列能否满足,事实上是不可能的,因为最小值一定要是另一个队列的结尾(不然就不止2个队列了),它也不满足(3)。
  
​   所以有\(f_{i,j}\rightarrow 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\n",ans);
}
int main(){
	readData();
	dp();
	return 0;
}

posted @ 2018-08-08 17:15  RogerDTZ  阅读(539)  评论(0编辑  收藏  举报