「也許,個人生命也已經如同風中殘燭了吧。」小綠如是說。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; }