若是一個序列知足序列長度爲\(n\),序列中的每一個數都是\(1\)到\(m\)內的整數,且全部\(1\)到\(m\)內的整數都在序列中出現過,則稱這是一個挺好序列。html
對於一個序列\(A\),記\(fA(l,r)\)爲\(A\)的第\(l\)個到第\(r\)個數中最大值的下標(若是有多個最大值,取下標最小的)。c++
兩個序列\(A\)和\(B\)同構,當且僅當\(A\)和\(B\)長度相等,且對於任意\(i≤j\),均有\(fA(i,j)=fB(i,j)\)。函數
給出\(n,m\),求有多少種不一樣構的挺好序列。答案對\(998244353\)取模。spa
一行兩個正整數\(n,m\)。code
一行一個整數,表示有多少種不一樣構的挺好序列。htm
3 2
4
顯然\(n<m\)時答案爲\(0\)。blog
解決這種題的思路就是找一個「等價條件」來計數。關於區間最大值的問題,咱們能夠用笛卡爾樹。get
對於一個笛卡爾樹的每一個節點\(v\),\(ls_v<v,rs_v\leq v\)。因此,若是一個笛卡爾樹的最長左鏈,也就是根到某個點的路徑上通過的左兒子數量(加上根)超過\(m\)的話,那麼就是無解的,由於此時至少要分配\(m+1\)個不一樣的權值。知足條件的話就必定有解。input
有一個很是厲害的\(O(nlogn)\)的生成函數作法能夠參考這篇博客https://www.cnblogs.com/Mr-Spade/p/10215081.html。博客
其實還有個\(O(n)\)的作法。
一棵多叉樹惟一對應一棵二叉樹,反過來也是惟一對應的。一棵\(n\)個二叉樹對應一棵\(n+1\)的點的多叉樹(要補一個根)。原來的二叉樹最長左鏈\(\leq m\),對應多叉樹的最大深度\(\leq m\)(根深度爲\(0\))。
考慮用括號序來解決這個問題。一棵\(n+1\)個點的樹的能夠表示爲\((X)\),其中\(X\)表示一個括號序列,左右兩個括號是根,因此咱們先將其刪除,因而對應了一個\(n\)對括號的括號序列。咱們設\((\)爲\(+1\),\()\)爲\(-1\),那麼樹的最大深度就是最大的前綴和,因此任意位置的前綴和\(x\)要知足\(0\leq x\leq m\)。
咱們將這個東西抽象到一個座標系上。初始起點在\((0,0)\),每次操做使橫座標\(+1\),縱座標能夠\(+1\)或者\(-1\)。要求任意時刻這個點不能達到\(y=m+1\)和\(y=-1\)這兩條直線,求最終走到\((2n,0)\)的方案數。
若是沒有任何限制,那麼從\((0,0)\)走到\((n,m)\)的方案數是\(C_{n}^{\frac{n+m}{2}}\),設這個東西爲\(path(n,m)\)
若是隻有一個限制,好比\(y=-1\),那麼咱們能夠用折線定理。將\((0,0)\)沿\(y=-1\)對稱到\((0,-2)\),每一條\((0,-2)\to (n,m)\)的路徑都惟一對應了原問題的一個非法路徑。考慮將第一次達到\(y=-1\)的路徑沿着\(y=-1\)對摺回來就能夠理解了。因此答案爲\(path(n,m)-path(n,m+2)\)。
若是有兩條線,那麼就要容斥了。設這條線分別爲\(a,b\)。一個非法序列能夠表示爲相似於\(aaabbb...aaabbb\)的一個序列,表示通過這兩條線的狀況。因而咱們枚舉這個序列的一個子序列\(ababab...\)。而後算出必定包含這個子序列的非法序列數。以包含\(ab\)的序列爲例,咱們先將起點\(p\)沿\(a\)翻折獲得\(p'\),而後再將\(p'\)沿\(b\)翻折獲得\(p''\),而後算出\(p''\)到\((2n,0)\)的方案數\(path(2n,0-{p''}_y)\)。計算跟複雜的序列就屢次翻折。容斥係數爲\((-1)^k\),\(k\)爲\(a,b\)的個數和。咱們能夠發現,\(p\)通過一次翻折,\(p_y\)就會增大,當\(p_y>n\)時,\(path\)必定爲\(0\)。
代碼:
#include<bits/stdc++.h> #define ll long long #define N 20000005 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; int fac[N],ifac[N]; 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,m; void pre(int n) { fac[0]=1; for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod; ifac[n]=ksm(fac[n],mod-2); for(int i=n-1;i>=0;i--) ifac[i]=1ll*ifac[i+1]*(i+1)%mod; } ll C(int n,int m) {return n<m?0:1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod;} ll cal(int n,int m) {return n<m?0:C(n,n/2+m/2);} ll ans; int main() { pre(2e7); n=Get(),m=Get(); if(n<m) {cout<<0;return 0;} n=n*2,m++; ans=cal(n,0); ans=(ans-cal(n,2*m)-cal(n,2))%mod; for(ll i=2*m+2;i<=n;i+=2*m+2) { ans=(ans+2*cal(n,i)-cal(n,i+2*m)-cal(n,i+2))%mod; } cout<<(ans+mod)%mod; return 0; }