zoukankan      html  css  js  c++  java
  • 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),也就是只考虑第一次碰到直线的贡献。不合法方案形如:

    [largeegin{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)=sumlimits_{i geqslant 0}f_{i,j}x^i),得:

    [largeegin{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)}),得:

    [largeegin{aligned} F_j(x)&=frac{1}{1-xF_{j-1}(x)} \ frac{A_j(x)}{B_j(x)}&=frac{1}{1-xfrac{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;
    }
    
  • 相关阅读:
    什么叫精通C++
    编程好书推荐
    Reading Notes ofC Traps and Pitfalls
    继承的小问题
    关于strcpy函数
    #pragma once 与 #ifndef 的区别解析
    模板类的友元重载函数
    NET开发人员必知的八个网站
    获取MDI窗体的实例
    .Net下收发邮件第三方公共库
  • 原文地址:https://www.cnblogs.com/lhm-/p/13823293.html
Copyright © 2011-2022 走看看