Loj #2554. 「CTSC2018」青蕈領主

Loj #2554. 「CTSC2018」青蕈領主

題目描述

「也許,個人生命也已經如同風中殘燭了吧。」小綠如是說。c++

小綠同窗由於微積分這門課,對「連續」這一律念產生了濃厚的興趣。小綠打算把連續的概念放到由整數構成的序列上,他定義一個長度爲 \(m\) 的整數序列是連續的,當且僅當這個序列中的最大值與最小值的差,不超過\(m-1\)。例如 \(\{1,3,2\}\) 是連續的,而 \(\{1,3\}\) 不是連續的。數組

某天,小綠的頂頭上司板老大,給了小綠 \(T\) 個長度爲 \(n\) 的排列。小綠拿到以後十分歡喜,他求出了每一個排列的每一個區間是不是他所定義的「連續」的。然而,小綠以爲被別的「連續」區間包含住的「連續」區間不夠優秀,因而對於每一個排列的全部右端點相同的「連續」區間,他只記錄下了長度最長的那個「連續」區間的長度。也就是說,對於板老大給他的每個排列,他都只記錄下了在這個排列中,對於每個 \(1 \le i \le n\),右端點爲 \(i\) 的最長「連續」區間的長度 \(L_i\)。顯然這個長度最少爲 \(1\),由於全部長度爲 \(1\) 的整數序列都是連續的。測試

作完這一切後,小綠爬上綠色牀,美美地作了一個綠色的夢。優化

但是次日醒來以後,小綠驚訝的發現板老大給他的全部排列都不見了,只剩下他記錄下來的 \(T\) 組信息。小綠知道本身在劫難逃,可是做爲一個好奇的青年,他仍是想知道:對於每一組信息,有多少個和信息符合的長度爲 \(n\) 的排列。spa

因爲小綠已經放棄治療了,你只須要告訴他每個答案對 \(998244353\) 取模的結果。code

咱們並不保證必定存在至少一個符合信息的排列,由於小綠也是人,他也有可能犯錯。遞歸

輸入格式

輸入的第一行包含兩個整數 \(T,n\),分別表示板老大給小綠的排列個數、以及每一個排列的長度。get

接下來 \(T\) 行,每行描述一組信息,包含 \(n\) 個正整數,第 \(i\) 組信息的從左往右第 \(j\) 個整數 \(L_{i,j}\) 表示第 \(i\) 個排列中右端點爲第 \(j\) 個數的最長「連續」區間的長度。it

對於每一行,若是行內包含多個數,則用單個空格將它們隔開。class

輸出格式

對於每組信息,輸出一行一個整數表示可能的排列個數對 \(998244353\) 取模的結果。因爲是計算機幫你算,因此咱們不給你犯錯的機會。

數據範圍與提示

對於全部測試數據,\(1 \le T \le 100\)\(1 \le N \le 50000\), \(1 \le L_{i,j} \le j\)


首先咱們獲得不少段區間,這些區間要麼相離,要麼包含,而且必定有一個\([1,n]\)。就用這兩個條件來判斷無解。而後咱們能夠將這些區間建成一個樹。

考慮區間\([l,r]\),他有\(k\)個兒子,因而咱們要將一段長爲\(r-l+1\)的連續區間分配個這\(k\)個兒子。首先這\(k\)個兒子每個都是連續的一段,而且相鄰的兒子不能組成連續的一段。可是考慮放在\(r\)位置上的那個點,他與全部兒子共同組成了連續的一段。若是說咱們把每一個兒子看作一個點而,再把\(r\)也看作一個點,那麼合法條件就是:一個\(k+1\)的排列,不能存在不包含最後一個位置的長度\(>1\)的連續區間。

設該答案爲\(f_k\),那麼:
\[ f_{n}=(n-1)f_{n-1}+\sum_{j=2}^{n-2}(j-1)f_jf_{i-j}\\ \]
特別地,\(f_0=1,f_1=2\)

咱們設一個合法數列爲\(A\),再令\(b_{a_i}=i\)。很容易發現\(A\)\(B\)是惟一映射的。\(A\)的合法條件在\(B\)數組中等價爲不能存在不包含最大那個元素的長度\(\geq 2\)的連續區間。

考慮從大到小插入每個數。已經插入了\([2,n+1]\),如今要插入\(1\)。若是原來的序列已經合法,那麼\(1\)只要不與\(2\)相鄰,這個數列依舊合法。這樣就還有\(n-1\)個位置能夠插入,因此有\((n-1)f_{n-1}\)。若是原來的數列不合法,那麼咱們要插入\(1\)破壞那個長度\(>1\)的連續區間。很顯然,不相交的連續區間最多有一個,否則插入一個\(1\)解決不了問題。因而咱們枚舉最大的那個非法區間的長度,設其爲\(j\),則\(2\leq j\leq n-2\)(首先至少有\(2\)兩個元素,而且不能讓最大的那個元素單獨存在,不然就不是最長的了)。假設非法區間的元素爲\([x\ldots x+j-1]\),那麼\(x\)\([2,n-j]\)\(n-j-1\)種方案,因此乘上係數\(n-1-j\)。考慮將\(1\)插入其中,等價於將\(j+1\)插入\([1,j]\)中,因此合法方案數爲\(f_j\)。考慮這段連續區間(插入\(1\)以前)必須是極長,因此若是將這段連續區間視爲一個點,那麼就有還有\(n-j\)個元素,合法的排列方案數是\(f_{n-j}\)。這部分貢獻爲
\[ \sum_{j=2}^{n-2}(n-j-1)f_jf_{n-j}=\sum_{j=2}^{n-2}(j-1)f_jf_{n-j} \]
發現這個方程能夠用分治\(FFT\)優化,不過這個寫法有點巧妙。

考慮分治\(FFT\)的原理是遞歸區間\([l,r]\)的時候將區間分爲了兩半,考慮計算左半邊對右半邊的貢獻。因而咱們發現,計算\([l,mid]\)\([mid+1,r]\)的貢獻時,咱們要用到\(f_{2\ldots r-l}\),可是可能\(mid<r-l\)。咱們考慮\(i,j(i<j)\),顯然只會在遞歸到某一個區間\([l,r]\)的時候纔會計算\(i\)\(j\)的貢獻。假設這個區間是\([l,mid,r]\),若是\(l\leq r-l\leq mid\),那麼顯然就能夠用\(f_{l\ldots mid}\)本身卷本身就好了。若是\(r-l<l\),那麼咱們就用\(f_{l\ldots mid}\)\(f_{2\ldots \min\{r-l,l-1\}}\)就行了。由於:
\[ (j-1)f_jf_{i-j}+(i-j-1)f_{i-j}f_j=(i-2)f_jf_{i-j} \]
因此對於計算了\(r-l<l\)的部分後乘上\(i-2\)就能夠一併計算\(r-l>mid\)的部分了。

代碼:

#include<bits/stdc++.h>
#define ll long long
#define N 50005

using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}

const ll mod=998244353;
ll ksm(ll t,ll x) {
    ll ans=1;
    for(;x;x>>=1,t=t*t%mod)
        if(x&1) ans=ans*t%mod;
    return ans;
}

int n;
int p[N];
void NTT(ll *a,int d,int flag) {
    static int rev[N<<2];
    static ll G=3;
    int n=1<<d;
    for(int i=0;i<n;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<d-1);
    for(int i=0;i<n;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
    for(int s=1;s<=d;s++) {
        int len=1<<s,mid=len>>1;
        ll w=flag==1?ksm(G,(mod-1)/len):ksm(G,mod-1-(mod-1)/len);
        for(int i=0;i<n;i+=len) {
            ll t=1;
            for(int j=0;j<mid;j++,t=t*w%mod) {
                ll u=a[i+j],v=a[i+j+mid]*t%mod;
                a[i+j]=(u+v)%mod;
                a[i+j+mid]=(u-v+mod)%mod;
            }
        }
    }
    if(flag==-1) {
        ll inv=ksm(n,mod-2);
        for(int i=0;i<n;i++) a[i]=a[i]*inv%mod;
    }
}

ll f[N];
ll A[N<<2],B[N<<2];

void solve(int l,int r) {
    if(l==r) {
        (f[l]+=f[l-1]*(l-1))%=mod;
        return ;
    }
    int mid=l+r>>1;
    int d=ceil(log2(r-l+1));
    solve(l,mid);
    for(int i=0;i<1<<d;i++) A[i]=B[i]=0;
    for(int i=l;i<=mid;i++) {
        A[i-l]=(i-1)*f[i]%mod;
        B[i-l]=f[i];
    }
    NTT(A,d,1),NTT(B,d,1);
    for(int i=0;i<1<<d;i++) A[i]=A[i]*B[i]%mod;
    NTT(A,d,-1);
    for(int i=0;i<1<<d;i++) 
        if(mid<i+2*l&&i+2*l<=r) (f[i+2*l]+=A[i])%=mod;
    
    int len=min(r-l,l-1);
    d=ceil(log2(len+mid-l+1));
    for(int i=0;i<1<<d;i++) A[i]=B[i]=0;
    for(int i=l;i<=mid;i++) A[i-l]=f[i];
    for(int i=2;i<=len;i++) B[i-2]=f[i];
    NTT(A,d,1),NTT(B,d,1);
    for(int i=0;i<1<<d;i++) A[i]=A[i]*B[i]%mod;
    NTT(A,d,-1);
    for(int i=0;i<1<<d;i++)
        if(mid<i+2+l&&i+2+l<=r) (f[i+2+l]+=(i+l)*A[i])%=mod;
    solve(mid+1,r);
}

int st[N],top;
int sn[N];

int main() {
    int T=Get();
    n=Get();
    f[0]=1,f[1]=2;
    if(n-1>2) solve(2,n-1);
    while(T--) {
        for(int i=1;i<=n;i++) p[i]=i-Get()+1;
        for(int i=1;i<=n;i++) sn[i]=0;
        if(p[n]!=1) {
            cout<<0<<"\n";
            continue ;
        }
        int flag=0;
        st[top=1]=n;
        for(int i=n-1;i>=1;i--) {
            while(p[st[top]]>i) top--;
            sn[st[top]]++;
            if(p[st[top]]>p[i]) {
                flag=1;
                break;
            }
            st[++top]=i;
        }
        if(flag) {
            cout<<0<<"\n";
        } else {
            ll ans=1;
            for(int i=1;i<=n;i++) ans=ans*f[sn[i]]%mod;
            cout<<ans<<"\n";
        }
    }
    return 0;
}
相關文章
相關標籤/搜索