UOJ424 [集训队作业2018] count

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

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

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

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

ABA BB AA B AB A BA B A BB A B AA B A B A

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

m 为两直线间的距离,得复杂度为 O(nm)

还有另一种做法,设 fi,ji 个节点往左走的次数 j 的笛卡尔树个数,得:

fi,j=k<ifk,j1fik1,j

其生成函数为 Fj(x)=i0fi,jxi,得:

Fj(x)=xFj1(x)Fj(x)+1Fj(x)=11xFj1(x)

Fj(x)=Aj(x)Bj(x),得:

Fj(x)=11xFj1(x)Aj(x)Bj(x)=11xAj1(x)Bj1(x)Aj(x)Bj(x)=Bj1(x)Bj1(x)xAj1(x)

Aj(x)=Bj1(x),Bj(x)=Bj1(x)xAj1(x),可以用矩阵快速幂求出 Am(x),Bm(x),这里先用点值表示后再进行快速幂即可。

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

#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; }

__EOF__

本文作者lhm_
本文链接https://www.cnblogs.com/lhm-/p/13823293.html
关于博主:sjzez 的一名 OI 学生
版权声明:转载标明出处
声援博主:希望得到宝贵的建议
posted @   lhm_liu  阅读(251)  评论(0编辑  收藏  举报
编辑推荐:
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
阅读排行:
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· 程序员常用高效实用工具推荐,办公效率提升利器!
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 【译】WinForms:分析一下(我用 Visual Basic 写的)
点击右上角即可分享
微信分享提示