JZOJ 5988. 【WC2019模拟2019.1.4】珂学计树题

Description

liu_runda曾经是个喜欢切数数题的OIer,往往看到数数题他就开始刚数数题.于是liu_runda出了一个数树题.听说OI圈子珂学盛行,他就在题目名字里加了珂学二字.一开始liu_runda想让选手数n个节点的不同构的二叉树的数目.
但是liu_runda虽然退役已久,也知道答案就是Catalan(n),这太裸了,出出来一定会被挂起来裱.因此他把题目加强.我们从二叉树的根节点出发一直向右儿子走到不能再走为止,可以找到最右下方的节点v,这个节点是没有右儿子的.
如果根节点和v不相同,我们就把根节点和根节点的右儿子断开,让根节点的右儿子成为新的根节点,同时把根节点接在v的右儿子位置.根节点的左儿子此时仍然挂在根节点上.
这样的操作可以进行多次.如果两棵二叉树能通过若干次这样的操作变得同构,我们也认为它们是同构的.
问在这种新的定义下有多少n个节点的本质不同的二叉树.答案可能很大,所以只需要输出对998244353取模后的结果.

Input

输入文件tree.in包含一行一个整数n

Output

输出文件为tree.out 输出一行一个整数表示答案模998244353的结果.

Sample Input

输入1:

3

输入2:

6

输入3:

20

输入4:

900000

Sample Output

输出1:

4

输出2:

80

输出3:

451434801

输出4:

255023975

Data Constraint

Data Constraint

Hint

Hint

Solution

  • 朴素做法是枚举右侧链长度,再往上面接一个个的子树(不能循环同构)。

  • 但是判断循环同构比较困难,要用 burnside引理 来解决,那还不如直接上正解。

  • 经过题目转换,原树可以转化成一棵二叉树(左儿子右兄弟),考虑其括号序列()()()。

  • 对应过来相当于是计算本质不同的 n n n 0 0 0 n n n 1 1 1 的序列个数。

  • 可以证明一种 0/1 序列一定是对应一种合法的括号序列的,循环几次一定合法了。

  • 这个就可以直接套 burnside引理 了。

  • burnside引理 定义:对于一个置换 f f f ,若一个染色方案 s s s 经过置换后不变,称 s s s f f f 的不动点。将 f f f 的不动点数目记为 C ( f ) C(f) C(f),则可以证明等价类数目为所有 C ( f ) C(f) C(f) 的平均值。

  • 先枚举其置换长度 i i i ,则答案为: ∑ i = 1 2 n C ( i , 2 n ) ( i , 2 n ) 2 \sum_{i=1}^{2n}C^{\frac{(i,2n)}{2}}_{(i,2n)} i=12nC(i,2n)2(i,2n)

  • 其中 ( a , b ) (a,b) (a,b) 表示 a , b a,b a,b 的最大公约数。

  • 由于 ( i , 2 n ) 2 \frac{(i,2n)}{2} 2(i,2n) 要整除,于是 i i i 必须为偶数,转换枚举得: ∑ i = 1 n C 2 ( i , n ) ( i , n ) \sum_{i=1}^{n}C^{(i,n)}_{2(i,n)} i=1nC2(i,n)(i,n)

  • d = ( i , n ) d=(i,n) d=(i,n) ,则上式 = ∑ i = 1 n C 2 d d = ∑ d ∣ n φ ( n d ) ∗ C 2 d d =\sum_{i=1}^{n}C_{2d}^{d}=\sum_{d|n}φ(\frac{n}{d})*C_{2d}^{d} =i=1nC2dd=dnφ(dn)C2dd

  • 因为 ( n d , i d ) = 1 (\frac{n}{d},\frac{i}{d})=1 (dn,di)=1 ,相当于是 n d \frac{n}{d} dn 的约数的都会被统计到,于是系数即为 φ ( n d ) φ(\frac{n}{d}) φ(dn)

  • 所以我们先预处理阶乘、逆元,线筛出 φ φ φ

  • 之后我们直接枚举 d d d ,计算即可,时间复杂度 O ( N ) O(N) O(N)

Code

#include<cstdio>
using namespace std;
typedef long long LL;
const int N=1e6+5,mo=998244353;
int n,ans;
int f[N<<1],g[N],phi[N];
inline int ksm(int x,int y)
{
	int s=1;
	while(y)
	{
		if(y&1) s=(LL)s*x%mo;
		x=(LL)x*x%mo;
		y>>=1;
	}
	return s;
}
inline int C(int x,int y)
{
	return (LL)f[x]*g[y]%mo*g[x-y]%mo;
}
int main()
{
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	scanf("%d",&n);
	phi[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!phi[i])
		{
			g[++g[0]]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=g[0] && i*g[j]<=n;j++)
			if(i%g[j]==0)
			{
				phi[i*g[j]]=phi[i]*g[j];
				break;
			}else
				phi[i*g[j]]=phi[i]*(g[j]-1);
	}
	f[0]=g[0]=1;
	for(int i=1;i<=n*2;i++) f[i]=(LL)f[i-1]*i%mo;
	g[n]=ksm(f[n],mo-2);
	for(int i=n-1;i;i--) g[i]=(LL)g[i+1]*(i+1)%mo;
	for(int i=1;i<=n;i++)
		if(n%i==0) ans=(ans+(LL)phi[n/i]*C(2*i,i))%mo;
	ans=(LL)ans*ksm(2*n,mo-2)%mo;
	printf("%d",ans);
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值