【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);
}

猜你喜欢

转载自www.cnblogs.com/chen1352/p/9099499.html
068