铁一中18年8.23模拟赛T1

题意

给你1~n的排列,全部插入双端队列,最终队列必须满足1的左边数列递减,右边递增,即一个V型,再以任意顺序全部弹出。问第k个弹出的数为1的方案有多少种(mod 1e9+7),注意,两个方案当且仅当它们某一次弹出的数不同时视为不同。

题解

首先我们要推出个有用的结论来转化题意,在这里,我们的重点是每次弹出的数。我们注意到每次能弹出的数为队首或队尾,要么是左边最大,要么是右边最大,显然其中一个必然是剩下全部数中最大的,不妨设是队首。又因为以前左边弹出的数都比队首大,右边弹出的数都比队尾大,所以队尾必然比所有以前弹出的所有数都小,所以队尾必然比最小值小。整理一下,每次能弹出的数只有两种可能
1. 没弹的数中最大
2. 比已弹出的数中最小还要小

d p [ i ] [ j ] 表示已弹i个数,这i个数的最小值为j的方案数。

转移时有两种情况:一种是这次(第 i 次)弹出j,那么只要上一次时最小值大于j即可,同时这时j满足条件2,所以可以弹出j;因为是排列,只有一个j,所以这种情况下方案数为 k = j + 1 n d p [ i 1 ] [ k ] 1

另一种是以前最小为j,这次弹出数比j大,要转移过来的状态显然为dp[i-1][j],但这时要满足 i + j 1 n 剩下的数中才有比j大的数,否则这种情况下方案数为0。在剩下的数中有比j大的数的情况下,条件2显然不能满足(要弹出的数比j大),对于dp[i-1][j]中每一种确定的方案,没弹出的数字也确定了,因为是排列,没弹的数中最大的数也只有对应的一个,所以这种情况下方案数为 d p [ i 1 ] [ j ] 1

那么最终方程为 d p [ i ] [ j ] = k = j n d p [ i 1 ] [ k ] ,用s[i]数组做前缀和优化,时间复杂度为O( n 2 )

代码

#include <iostream>
#include <stdio.h>
using namespace std;
#define N 2005
const int mod=1e9+7;
int n,k;
int dp[N][N],ss[N];

int MUL(int a,int b)
{
    int ret=0;
    while(b)
    {
        if((b&1))
            ret=(ret+a)%mod;
        b>>=1;
        a=(a+a)%mod;
    }
    return ret;
}

int KSM(int a,int p)
{
    int ret=1;
    while(p)
    {
        if((p&1))
            ret=MUL(ret,a);
        p>>=1;
        a=MUL(a,a);
    }
    return ret;
}

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
        dp[1][i]=1,ss[i]=n-i+1;
    for(int i=2;i<=k;i++)
        for(int j=n;j>=1;j--)
        {
            dp[i][j]=(i+j-1<=n?ss[j]:0);
            ss[j]=(ss[j+1]+dp[i][j])%mod;
        }
    printf("%d\n",MUL(KSM(2,n-k-1),((dp[k][1]-dp[k-1][1])%mod+mod)%mod));
    return 0;
} 

猜你喜欢

转载自blog.csdn.net/FatetoEternity/article/details/81987328