题面
思路
单调性
首先,显然可以发现这些数在放进双端队列之后肯定是一个$V$形的排布:1在最中间,两边的数都是单调递增
那么我们拿出来的数,显然也可以划分成2个单调递减的子序列(因为我们也是从两边往中间取的)
我们令这两个单调递减的子序列的最小值分别为$p$和$q$,其中$p<q$
那么,有一个结论:任意的的还没有被选取的数$k<q$,证明就是用上面的单调递减理论。
如果有一个数比$q$还要大,那么它肯定会在$p,q$之前被拿出队列,因此不会还没有被选取。
递推
考虑我们新放进一个数$k$
如果$p<k<q$,那么显然这个$k$只能放到$q$的那个单调序列的末尾此时这个单调序列的最小值就是$k$了,但是整个已经拿出来的集合的最小值还是$p$
如果$k<p$,那么$k$可以放到两个单调序列的任意一个的末尾。这两种情况下我们分别会得到$(k,p)$和$(q,k)$这两种最小值组合,此时集合最小值一定为$k$
可以发现,这两种情况中,改变了的都是最小值,而且两种情况之间的不同之处也在于新加入的元素和最小值的大小关系
那么,我们就可以考虑一个以当前取出的数集合大小和最小值为状态的$dp$
设$dp[i][j]$表示当前取出了$i$个数,其中的最小值是$j$
那么由上面的讨论,可以得到状态转移方程:
$dp[i][j]=dp[i-1][j]+\sum_{k>j}dp[i-1][k]=\sum_{k>=j}dp[i-1][k]$
到了K以后的问题
显然,上面方程中的$i$,取值范围是$[1,K)$
第$K$个数要求是放1,那么显然这个1只能放在最小值所在的那个序列末尾
后面剩下的$n-K$个数显然就随便取了,一共有$2^{n-K-1}$种选法(最后一个不用乘2)
那么答案就是$2^{n-K-1}\sum_{i=2}^{n-K+2} dp[K-1][i]$
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<cmath>
#define ll long long
#define MOD 1000000007
using namespace std;
inline ll read(){
ll re=0,flag=1;char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-') flag=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
return re*flag;
}
ll dp[2010][2010],p[2010],sum[2010][2010];
int main(){
ll n=read(),k=read();ll i,j;
p[0]=1;
for(i=1;i<=n;i++) p[i]=p[i-1]*2%MOD;
for(i=2;i<=n;i++) dp[1][i]=1,sum[1][i]=n-i+1;
for(i=2;i<k;i++){
for(j=n-i+1;j>=2;j--){
dp[i][j]=(dp[i-1][j]+sum[i-1][j+1])%MOD;
//这里我分开写了,这样清晰一点
}
for(j=n-i+1;j>=2;j--) sum[i][j]=(sum[i][j+1]+dp[i][j])%MOD;
}
ll ans=0;
for(i=2;i<=n-k+2;i++) ans+=dp[k-1][i],ans%=MOD;
if(k==1) ans=1;
if(k==n) printf("%lld\n",ans);
else printf("%lld\n",ans*p[n-k-1]%MOD);
}