【arc068F】Solitaire
题目大意
有一个队列,头尾都可以进出。
首先将n个数1~n从小到大扔进队列,然后将一次弹出队列,求最后弹出来的排列中,第k个数为1的排列有多少种。
解题思路
我们来考虑一下一个合法排列的性质,
第k个数是1
前k-1个数是可以拆成一个或两个单调递减的序列。
前k-1个数中其中一个序列的最小值一定大于后n-k个数中的最大值。
考虑如何来满足这个构造出这个排列。
先考虑后n-k-1个数,发现,这些数一定是有一个单调的队列,每次弹出头或尾来构成的,只要我们确定前k-1个数,就可以得出这个单调的队列能构成的后n-k-1个数的方案,就是\(2^{n-k-1}\)。
然后,如何确定前k-1个数,且保证第2条性质呢?
设f[i][j]表示,前k-1个数中,已确定了i个数,在确定的i个数中,最小值为j。
假定第一个单调序列是弹出1个一遍,那么第二个单调序列就要满足第3个性质。
每次新加一个数,如果加进第一个单调序列,显然加入的数就要小于j
于是f[i][j]->f[i+1][k] (j>k)
如果如果加进第二个单调序列,显然加入的数就要是当前没加的数中最大的,只有这样才能满足第3条性质。
于是f[i][j]->f[i+1][j]。
注意一下边界。
#include <cmath>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <bitset>
#include <set>
const int maxlongint=2147483647;
const long long mo=1e9+7;
const int N=3005;
using namespace std;
int n,k;
long long ans,mi[N],f[N][N];
int main()
{
scanf("%d%d",&n,&k);
mi[0]=1;
for(int i=1;i<=n;i++) mi[i]=mi[i-1]*2%mo;
for(int i=n;i>=2;i--) f[1][i]=1;
for(int i=1;i<k-1;i++)
{
long long sum=f[i][n-i+1];
for(int j=n-i;j>=2;j--)
{
sum=(sum+f[i][j])%mo;
f[i+1][j]=(f[i+1][j]+sum)%mo;
}
}
for(int j=2;j<=n-k+2;j++) ans=(ans+f[k-1][j])%mo;
if(k==1) ans=1;
if(n-1-k<0) printf("%lld",ans);
else
printf("%lld",ans*mi[n-1-k]%mo);
}