[NOI2018]冒泡排序

[NOI2018]冒泡排序 

题解

性质+模型转化

 

首先,一个排列是“好”的,当且仅当:每个数,要么是前缀最大值,要么是后缀最小值。(讨论i和Pi的关系即可证明)

也就是,排列不能存在>=3的下降子序列!

换句话说,假设之前填了i个数,最大值是mx,那么第i+1个数,要么是剩下数的最小值,要么是比mx大的数。

字典序,肯定按位考虑,转化成没有限制的情况,

所以先处理没有限制的情况

这样DP,

$f[i][j]$剩下i个数,比之前最大值大的数有j个的方案数。

第n-i+1个位置,要么填最小值,要么填这j个数之一。

填这j个中第k大的数(它就成为了新的最大值),就只能剩下j-k个比最大值大的了。

转移:$f[i][j]=\sum_{k=0}^{j}f[i-1][k]$,k=0代表填了最小值。前缀和优化

当然,i>=j必须保证

 

然后可以卡位。

之前比最大值大的数有nw个,第i个位置数是ai,

第i个位置:

1.$p_i>a_i$

$p_i$一定是一个比最大值大的数

如果$a_i$是前缀最大值,则nw=n-a[i]

否则,nw=n-前缀最大值

显然为了严格大于,如果nw=0,一定不行。

 

自由之后,方案数是:$\sum_{j=0}^{nw-1}f[n-i][j]=f[n-i+1][nw-1]$

2.$p_i=a_i$

判断$a_i$是不是前缀最大值或者后缀最小值即可

 

O(T*n^2)

过不去。

瓶颈在于O(n^2)DP

这个DP很模式化啊,,,

$f[i][j]=\sum_{k=0}^{j}f[i-1][k]$

能不能发现组合意义?

结论:

$f[i][j]=C(i+j-1,j)-C(i+j-1,j-2)$

证明:

显然必须有i>=j

本质是,(0,0)往(i,j)走,每次要么往右走,要么往右上走,不越过直线(i=j)的方案数。

可以对偶成:从(1,1)走,每次往右往上走,不能越过直线(i=j-1)的方案数

蓝色折线和绿色折线一一对应!

不合法的也一一对应!(请自行画图)

从(1,1)走,每次往右往上走,不能越过直线(i=j-1)的方案数

这个直接卡特兰数一样,对称容斥下即可。

 

O(n)

 

// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define reg register int
#define il inline
#define fi first
#define se second
#define mk(a,b) make_pair(a,b)
#define numb (ch^'0')
#define pb push_back
#define solid const auto &
#define enter cout<<endl
#define pii pair<int,int>
using namespace std;
typedef long long ll;
template<class T>il void rd(T &x){
    char ch;x=0;bool fl=false;while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);(fl==true)&&(x=-x);}
template<class T>il void output(T x){if(x/10)output(x/10);putchar(x%10+'0');}
template<class T>il void ot(T x){if(x<0) putchar('-'),x=-x;output(x);putchar(' ');}
template<class T>il void prt(T a[],int st,int nd){for(reg i=st;i<=nd;++i) ot(a[i]);putchar('\n');}
namespace Modulo{
const int mod=998244353;
il int ad(int x,int y){return x+y>=mod?x+y-mod:x+y;}
il int sub(int x,int y){return ad(x,mod-y);}
il int mul(int x,int y){return (ll)x*y%mod;}
il void inc(int &x,int y){x=ad(x,y);}
il void inc2(int &x,int y){x=mul(x,y);}
il int qm(int x,int y=mod-2){int ret=1;while(y){if(y&1) ret=mul(x,ret);x=mul(x,x);y>>=1;}return ret;}
template<class ...Args>il int ad(const int a,const int b,const Args &...args) {return ad(ad(a,b),args...);}
template<class ...Args>il int mul(const int a,const int b,const Args &...args) {return mul(mul(a,b),args...);}
}
using namespace Modulo;
namespace Miracle{
const int N=1200000+5;
int n;
int jie[N],inv[N];
int C(int n,int m){
    if(n<0||m<0||n<m) return 0;
    return mul(jie[n],inv[m],inv[n-m]);
}
int F(int n,int m){
    if(m==0) return 1;
    if(m==1) return n;
    return sub(C(n+m-1,m),C(n+m-1,m-2));
}
int a[N];
bool is[N];
int main(){
    int t;rd(t);
    n=N-3;
    jie[0]=1;
    for(reg i=1;i<=n;++i) jie[i]=mul(jie[i-1],i);
    inv[n]=qm(jie[n]);
    for(reg i=n-1;i>=0;--i) inv[i]=mul(inv[i+1],i+1);

    while(t--){
        rd(n);
        for(reg i=1;i<=n;++i) rd(a[i]);
        int mi=n+3;
        for(reg i=n;i>=1;--i){
            is[i]=0;
            if(a[i]<mi){
                mi=a[i];
                is[i]=1;
            }
        }
        int ans=0;
        int nw=n,mx=0;
        for(reg i=1;i<=n;++i){
            nw=n-mx;
            int lp=nw;
            if(a[i]>mx) lp=n-a[i]; 
            if(lp){
                ans=ad(ans,F(n-i+1,lp-1));
            }
            if(a[i]>mx){
                mx=a[i];
            }else if(!is[i]){
                break;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
*/

 

发现本质:一个数要么是前缀最大值,要么是后缀最小值

无限制?可以DP,f[i][j]记录剩下的数以及比最大值大的数

字典序?按位考虑。变成无限制。

O(n^2)?观察DP式子找到组合意义!

 

 

 

 

posted @ 2019-06-16 17:28  *Miracle*  阅读(381)  评论(0编辑  收藏  举报