[2018集訓隊做業][UOJ424] count [笛卡爾樹+括號序列+折線法+組合數學]

題面

請務必不要吐槽個人標籤ios

傳送門git

思路

一個很重要的結論:原序列的一組同構的解等價於同一棵擁有$n$個節點的笛卡爾樹spa

注意笛卡爾樹的定義:父親節點是區間最值,而且分割區間爲左右部分code

因此若是兩個序列的笛卡爾樹同構,那麼他們的每個區間最小值位置相同,也就是原題目中的同構條件了ip

一個很重要的結論:定義笛卡爾樹節點的深度爲根到這個節點的路徑上向左走的次數,那麼合法序列的笛卡爾樹全部節點深度不超過$m$get

首先,咱們能夠定義區間的父節點是全部最值中最靠左的,那麼容易獲得,節點的左兒子中的全部權值嚴格小於當前節點string

這樣,咱們往左走的次數一旦超過了$m$就意味着有$m$以上個不一樣的數出如今序列中it

反之,咱們能夠證實對於一個沒有深度超過$m$的節點的笛卡爾樹必定能構造出一組合法的,$m$個數都被用過的解io

首先,找到笛卡爾樹上的最深鏈(設其長度爲$len$),並把最長鏈上的節點構形成爲$n$到$n-len+1$class

而後,不斷尋找最深的沒有賦值的點,並賦值成爲當前沒有出現過的數中最小的

最後,對於仍然沒有賦值的點,從根開始,令他們等於父親的權值-1(注意根節點必定被賦值了)

一個很重要的結論:笛卡爾樹等價於一個括號序列

因而問題轉化爲:求合法的括號序列,使其任何一前綴中左括號減掉右括號都小於等於$m$,的數量

這個問題咱們能夠利用折線法方便的解決:

一個很重要的方法:折線法能夠解決括號序列問題

咱們令左括號爲$(+1,0)$,右括號爲$(0,+1)$

那麼顯然問題被轉化成了只在在$y=x$和$y=x+m$兩條直線中間運行,最後到達$(n,n)$的不一樣折線的數量

這個問題中,咱們能夠容斥:越過一次折線之後咱們就把終點關於那條折線對稱一下,並乘上一個(-1)的係數

具體詳見代碼

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#define MOD 998244353
#define ll long long
using namespace std;
inline int read(){
    int re=0,flag=1;char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(isdigit(ch)) re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
    return re*flag;
}
inline int qpow(int a,int b){
    int re=1;
    while(b){
        if(b&1) re=1ll*re*a%MOD;
        a=1ll*a*a%MOD;b>>=1;
    }
    return re;
}
int n,m,ans=0,f[1000010],finv[1000010];
inline void init(){
    int i,len=1000000;
    f[0]=f[1]=finv[0]=finv[1]=1;
    for(i=2;i<=len;i++) f[i]=1ll*f[i-1]*i%MOD;
    finv[len]=qpow(f[len],MOD-2);
    for(i=len;i>2;i--) finv[i-1]=1ll*finv[i]*i%MOD;
}
inline int C(int x,int y){
//  cout<<"C "<<x<<' '<<y<<' '<<f[x]<<' '<<finv[y]<<' '<<finv[x-y]<<'\n';
    return 1ll*f[x]*finv[y]%MOD*finv[x-y]%MOD;
}
inline void flip(int &x,int &y,int b){//關於折線翻轉
    int tx=x,ty=y;
    x=ty+b;
    y=tx-b;
}
int main(){
    n=read();m=read();
    if(n<m){puts("0");return 0;}
    init();
    ans=C(n<<1,n);
    int i,x1=0,x2=0,y1=0,y2=0,d1=1,d2=-m-1;
    for(i=-1;i;i=-i){
        flip(x1,y1,(i>0?d1:d2));//這是兩個不一樣的翻轉方向
        flip(x2,y2,(i>0?d2:d1));
        if((x1>n||y1>n)&&(x2>n||y2>n)) break;
        if(x1<=n&&x1>=-n) ans=((ans+i*C(n<<1,n-x1))%MOD+MOD)%MOD;
        if(x2<=n&&x2>=-n) ans=((ans+i*C(n<<1,n-x2))%MOD+MOD)%MOD;
    }
    cout<<ans<<'\n';
}
相關文章
相關標籤/搜索