[NOI2018]冒泡排序

[NOI2018]冒泡排序


神题orz

题目里的这个限制实际上是不存在长度为3的下降子序列

先看没有字典序要求怎么做。

首先\(O(n^2)\)递推式(我太弱了这都不会...)

不存在长度为3的下降子序列那么序列可以分成两个上升序列。

\(f[i][j]\)表示\(i\)个数有\(j\)个大于填过的最大值。

由于可以分成两个上升序列,所以小于填过的最大值的这些数一定会从小到大填(凑进一个上升序列)大于的可以随便填

那么转移方程是\(f[i][j]=\sum_{k\leq j}f[i-1][k]\)

\(f[i-1][j]\)转移过来就是填一个小于填过的最大值的,否则是填\(1\)个大于当前最大值的,在所有满足大于当前最大值的数中(从小到大)排名为\(j-k\)

另外还有条件\(i\leq j\),边界情况\(f[0][0]=1\)

转移方程是个前缀和,可以改写成\(f[i][j]=f[i-1][j]+f[i][j-1]\)

那么就是组合数学的那一套理论,从\((0,0)\)只走上,右走到\((i,j)\)不触碰线\(y=x+1\)的方案数,显然有\(f[i][j]=C_{i+j-1}^{i-1}-C_{i+j-1}^{i+1}\),答案是\(f[n][n]\)

那么有字典序要求怎么做,p表示字典序下界,q表示求出的排列。枚举\(i\),字典序要求满足\(\forall 1\leq j<i,p_j=q_j;p_i<q_i\)。设\(maxp=\max_{j=1}^ip_i\)而且已经检查过前\(i-1\)\(p\)合理。分类讨论:

  1. \(q_i<maxp\),可以发现是不行的,因为前\(i-1\)\(p\)要合理,从刚才的dp知道填小于当前最大值的数就会填没填过得最小数。\(p_i\)显然没有填过,这个没填过得小数上界就是\(p_i\)
  2. \(q_i>maxp\),那么要统计之后的方案数,可以发现是\(f[n-i][n-q_i]\)。总和还是前缀和,\(f[n-i+1][n-maxp-1]\)

然后要检查新加的\(p\)是否合理,如果新\(p\)不是最大值就检查\(p\)是否是没填过的数中的最小值。乱搞一下就行了。

#include<bits/stdc++.h>
#define il inline
#define vd void
#define mod 998244353
typedef long long ll;
il ll gi(){
    ll x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))f^=ch=='-',ch=getchar();
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return f?x:-x;
}
il int pow(int x,int y){
    int ret=1;
    while(y){
        if(y&1)ret=1ll*ret*x%mod;
        x=1ll*x*x%mod;y>>=1;
    }
    return ret;
}
int n,p[600010],t[600010];
int fact[1200010],ifact[1200010];
il int C(int n,int m){return 1ll*fact[n]*ifact[m]%mod*ifact[n-m]%mod;}
il int F(int i,int j){return (C(i+j-1,i-1)-C(i+j-1,i+1)+mod)%mod;}
int main(){
    int T=gi(),nn=1200000;
    fact[0]=1;for(int i=1;i<=nn;++i)fact[i]=1ll*fact[i-1]*i%mod;
    ifact[nn]=pow(fact[nn],mod-2);for(int i=nn-1;~i;--i)ifact[i]=1ll*ifact[i+1]*(i+1)%mod;
    while(T--){
        n=gi();
        for(int i=1;i<=n;++i)p[i]=gi();
        int maxp=0,ans=0;
        for(int i=1,s=0;i<=n;++i){
            maxp=std::max(p[i],maxp);
            if(maxp==n)break;
            ans=(ans+F(n-i+1,n-maxp-1))%mod;
            if(p[i]!=maxp&&s+1!=p[i])break;
            t[p[i]]=1;while(t[s+1])++s;
        }
        printf("%d\n",ans);
        for(int i=1;i<=n;++i)t[i]=0;
    }
    return 0;
}
posted @ 2019-06-09 15:22  菜狗xzz  阅读(288)  评论(0编辑  收藏  举报