【UOJ424】【集训队作业2018】count(容斥)

点此看题面

  • 求有多少棵不同的(n)个点的笛卡尔树,满足任意点到根路径上作为左儿子出现的点数小于(m)
  • (n,mle10^5)

二叉树转多叉树

考虑一个点和它右儿子到根路径上作为左儿子出现的点数是一样的。

如果我们把这个东西看作新的深度,建出一棵新树,那么原树中一条从右向下的链将会变成兄弟。(为此,我们还要增加一个虚拟根作为新树的根节点,以统辖根节点从右向下的那条链。)

容易发现,得到的这棵多叉树和原二叉树是一一对应的。

假设新的根深度为(0),那么原树的根在新树中的深度就变成了(1),新树中一个点的深度就成了原树中到根路径上作为左儿子出现的点数(+1)

因此,原树中的限制到了新树,就变成了深度不超过(m)

坐标系走路

直接考虑新树的括号序列,然后问题就转化成在坐标系上从((0,0))走到((2n,0)),每次走向右上/右下,不能触碰到(x=-1)(x=m+1)两条直线的方案数。

经典容斥问题,用假设触碰到了(x=-1)的方案数,减去在此基础上又触碰到了(x=m+1)的方案数,再加回在此二者基础上又触碰到了(x=-1)的方案数,如是重复,直至不可能。然后从假设触碰到了(x=m+1)的方案数开始再类似地容斥一遍。

具体地,我们记录终点坐标(T)(初始为((2n,0))),那么假设触碰到了(x=-1)就是找到终点坐标关于它的对称点(T')求出起点到它的方案数,在此基础上又触碰到了(x=m+1)就是找到(T')关于它的对称点(T'')求出起点到它的方案数,以此类推。而当终点纵坐标不在([-2n,2n])范围内就不可能了。

代码:(O(n))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define X 998244353
#define C(x,y) (1LL*Fac[x]*IFac[y]%X*IFac[(x)-(y)]%X)
using namespace std;
int n,m;I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
int Fac[2*N+5],IFac[2*N+5];I void InitFac(CI n)
{
	RI i;for(Fac[0]=i=1;i<=n;++i) Fac[i]=1LL*Fac[i-1]*i%X;
	for(IFac[i=n]=QP(Fac[n],X-2);i;--i) IFac[i-1]=1LL*IFac[i]*i%X;
}
I int Calc(CI y) {return -2*n<=y&&y<=2*n?C(n<<1,n+y/2):0;}//计算从(0,0)到(2n,y)的方案数
int main()
{
	RI i,y,t;if(scanf("%d%d",&n,&m),n<m) return puts("0"),0;InitFac(n<<1);t=Calc(0);
	y=0;W(-2*n<=y&&y<=2*n) y=-2-y,t=(t-Calc(y)+X)%X,y=2*(m+1)-y,t=(t+Calc(y))%X;//触碰x=-1,再触碰x=m+1,不断重复
	y=0;W(-2*n<=y&&y<=2*n) y=2*(m+1)-y,t=(t-Calc(y)+X)%X,y=-2-y,t=(t+Calc(y))%X;return printf("%d
",t),0;//触碰x=m+1,再触碰x=-1,不断重复
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/UOJ424.html