UOJ424 [集训队作业2018] count

将序列对应到笛卡尔树,发现每棵笛卡尔树只对应一种合法序列。因为在笛卡尔树上往左走其对应的数至少减 \(1\),往右走不一定减 \(1\),所以这棵笛卡尔树从根节点往左走的次数要 \(\leqslant m\),题目就转化为了统计有多少棵 \(n\) 个节点的合法笛卡尔树。

笛卡尔树是二叉树,因为二叉树和括号序列都可以用卡特兰数计数,所以笛卡尔树能转化为括号序列。考虑中序遍历,每往左走就加入一个左括号,回溯回来时加入一个右括号,往右走时不操作,到叶子节点时加入一对完整括号,那么其就对应到了一个 \(2n\) 的括号序列。

\(s_i\) 为位置 \(i\) 之前左括号个数减右括号个数,得其需要满足 \(\forall i \in [1,2n],0 \leqslant s_i \leqslant m\)。将其进一步转化为网格图上路径计数,即从 \((0,0)\) 走到 \((n,n)\),只能向上向右走,且不能碰到直线 \(A:y=x+1,B:y=x-m-1\) 的方案数。

考虑翻折法来容斥计数,将连续碰到一条直线看作只碰到一次,如 \(A\ A\ A\ B\ B\ A\) 看作 \(A\ B\ A\),也就是只考虑第一次碰到直线的贡献。不合法方案形如:

\[\large\begin{aligned} &A \\ &B \\ &A\ B \\ &B\ A \\ &A\ B\ A \\ &B\ A\ B \\ &A\ B\ A\ B \\ &B\ A\ B\ A \\ &A\ B\ A\ B\ A \\ &\dots \end{aligned} \]

考虑要减去以 \(A\) 开头的方案数,就减去以 \(A,A\ B\) 结尾的方案数,加上以 \(B\ A,B\ A\ B\) 结尾的方案数,一直这样下去,直到方案数为 \(0\)。这一过程可以看作将终点 \((x,y)\) 沿 \(A\) 翻折,减去从 \((0,0)\) 到终点不受限制的方案数,再沿 \(B\) 翻折,加上从 \((0,0)\) 到终点不受限制的方案数,直到终点 \((x,y)\) 超出边界。减去以 \(B\) 开头的方案数同理。

\(m\) 为两直线间的距离,得复杂度为 \(O(\frac{n}{m})\)

还有另一种做法,设 \(f_{i,j}\)\(i\) 个节点往左走的次数 \(\leqslant j\) 的笛卡尔树个数,得:

\[\large f_{i,j}=\sum_{k<i}f_{k,j-1}f_{i-k-1,j} \]

其生成函数为 \(F_j(x)=\sum\limits_{i \geqslant 0}f_{i,j}x^i\),得:

\[\large\begin{aligned} F_j(x)&=xF_{j-1}(x)F_j(x)+1 \\ F_j(x)&=\frac{1}{1-xF_{j-1}(x)} \\ \end{aligned} \]

\(F_j(x)=\frac{A_j(x)}{B_j(x)}\),得:

\[\large\begin{aligned} F_j(x)&=\frac{1}{1-xF_{j-1}(x)} \\ \frac{A_j(x)}{B_j(x)}&=\frac{1}{1-x\frac{A_{j-1}(x)}{B_{j-1}(x)}} \\ \frac{A_j(x)}{B_j(x)}&=\frac{B_{j-1}(x)}{B_{j-1}(x)-xA_{j-1}(x)} \\ \end{aligned} \]

\(A_j(x)=B_{j-1}(x),B_{j}(x)=B_{j-1}(x)-xA_{j-1}(x)\),可以用矩阵快速幂求出 \(A_m(x),B_m(x)\),这里先用点值表示后再进行快速幂即可。

复杂度为 \(O(n \log n)\),明显没有第一种方法优秀。

#include<bits/stdc++.h>
#define maxn 400010
#define all 400000
#define p 998244353
using namespace std;
typedef long long ll;
template<typename T> inline void read(T &x)
{
    x=0;char c=getchar();bool flag=false;
    while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    if(flag)x=-x;
}
int n,m,x,y;
ll ans;
ll fac[maxn],ifac[maxn];
ll inv(ll x)
{
    ll v=1,y=p-2;
    while(y)
    {
        if(y&1) v=v*x%p;
        x=x*x%p,y>>=1;
    }
    return v;  
}
ll C(int n,int m)
{
    return (n<m||n<0||m<0)?0:fac[n]*ifac[m]%p*ifac[n-m]%p;
}
void c1(int &x,int &y)
{
    swap(x,y),x--,y++;
}
void c2(int &x,int &y)
{
    swap(x,y),x+=m+1,y-=m+1;
}
void init()
{
    fac[0]=ifac[0]=1;
    for(int i=1;i<=all;++i) fac[i]=fac[i-1]*i%p;
    ifac[all]=inv(fac[all]);
    for(int i=all-1;i;--i) ifac[i]=ifac[i+1]*(i+1)%p;
}
int main()
{
    init(),read(n),read(m),ans=C(2*n,n);
    if(n<m)
    {
        puts("0");
        return 0;
    }
    x=y=n;
    while(x>=0&&y>=0) c1(x,y),ans=(ans+p-C(x+y,y))%p,c2(x,y),ans=(ans+C(x+y,y))%p;
    x=y=n;
    while(x>=0&&y>=0) c2(x,y),ans=(ans+p-C(x+y,y))%p,c1(x,y),ans=(ans+C(x+y,y))%p;
    printf("%lld",ans);
    return 0;
}
posted @ 2020-10-15 21:30  lhm_liu  阅读(239)  评论(0编辑  收藏  举报