UOJ#424. 【集訓隊做業2018】count

UOJ#424. 【集訓隊做業2018】count

若是一個序列知足序列長度爲\(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

樣例一

input

3 2

output

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;
}
相關文章
相關標籤/搜索