歌名 - 歌手
0:00

    【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);
    }
    
    posted @ 2018-05-28 12:13  无尽的蓝黄  阅读(393)  评论(0编辑  收藏  举报