[JZOJ5988]【WC2019模拟2019.1.4】珂学计树题【Burnside引理】【排列组合】【括号序】

Description

从一棵二叉树的根节点出发一直向右儿子走到不能再走为止,可以找到最右下方的节点v,这个节点是没有右儿子的.
如果根节点和v不相同,我们就把根节点和根节点的右儿子断开,让根节点的右儿子成为新的根节点,同时把根节点接在v的右儿子位置.根节点的左儿子此时仍然挂在根节点上.
这样的操作可以进行多次.如果两棵二叉树能通过若干次这样的操作变得同构,我们也认为它们是同构的.
问在这种新的定义下有多少n个节点的本质不同的二叉树.答案可能很大,所以只需要输出对998244353取模后的结果.

n ≤ 1 e 6 n\leq 1e6 n1e6

Solution

这其实是一道OEIS题

这棵树可以看成是一条右链,每个点左边挂一个大小未知(可以为0)的二叉树,要求总结点数为0,并且如果在右链上循环同构的话只算一种。

想到卡特兰数,我就想到括号序,就想到今年下半年…

有根树转为二叉树,我们有一个很经典的做法就是左儿子右兄弟法,那么我们现在将这个过程逆过来,将原二叉树转换成一个森林,右链上的点就是森林中每棵树的根,显然这样一个n个点的森林唯一对应一个长度为2n的合法括号序列(考虑每个点进入为左括号,出去为右括号)。

森林中每棵树之间循环同构,实际上就是合法的括号序之间循环同构,不合法的不能构成一棵森林。

现在我们只需要计算这玩意(n个左括号,n个右括号构成合法括号序循环同构的等价类)。
直接用burnside好像有问题??
因为burnside计算的是所有循环同构的等价类数量,但我们需要排除掉那些不合法的括号序。

然而实际上并不需要
我们考虑不合法的括号序,那么就是在某个位置出现了一个右括号没有左括号与之匹配,那么这个位置+1开始的后缀左括号数一定是多1的,我们可以将这个后缀旋转到前面去,那么又变成一个合法序列了。

也就是说,一个不合法的括号序至少与一个合法的括号序循环同构,直接套用burnside等价类数是一样的。

现在的问题就变成了有n个0,n个1组成一个序列,问循环同构的等价类数

枚举旋转了i步,那么全部位置分成了 gcd ⁡ ( 2 n , i ) \gcd(2n,i) gcd(2n,i)组,每组中颜色全部相同,每一组的大小相同
又因为必须n个0,n个1,因此i也必须为偶数,所有组对半分为0和1
a n s = 1 2 n ∑ i = 1 2 n [ i 为 偶 数 ] ( gcd ⁡ ( 2 n , i ) gcd ⁡ ( 2 n , i ) 2 ) ans={1\over 2n}\sum\limits_{i=1}^{2n}[i为偶数]{\gcd(2n,i)\choose {\gcd(2n,i)\over 2}} ans=2n1i=12n[i](2gcd(2n,i)gcd(2n,i))

i ′ = 2 i , d = gcd ⁡ ( i ′ , n ) i'=2i,d=\gcd(i',n) i=2id=gcd(i,n)
枚举d
交换主体,化简有
a n s = 1 2 n ∑ d ∣ n ( 2 d d ) ∑ i = 1 n [ gcd ⁡ ( i , n ) = = 1 ] = 1 2 n ∑ d ∣ n ( 2 d d ) φ ( n d ) ans={1\over 2n}\sum\limits_{d|n}{2d\choose d}\sum\limits_{i=1}^{n}[\gcd(i,n)==1]={1\over 2n}\sum\limits_{d|n}{2d\choose d}φ\left({n\over d}\right) ans=2n1dn(d2d)i=1n[gcd(i,n)==1]=2n1dn(d2d)φ(dn)

这样用线性筛法预处理phi,再线性预处理阶乘和逆元。
时间复杂度 O ( n ) O(n) O(n)

Code

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 2000005
#define mo 998244353
#define LL long long
using namespace std;
int n,n1,pr[N];
LL js[N],ny[N],s2,ns,phi[N];
bool bz[N];
LL C(int n,int m)
{
	if(n<m) return 0;
	return js[n]*ny[m]%mo*ny[n-m]%mo;
}
LL ksm(LL k,LL n)
{
	LL s=1;
	for(;n;n>>=1,k=k*k%mo) if(n&1) s=s*k%mo;
	return s;
}
void prp()
{
	phi[1]=1;
	fo(i,2,n)
	{
		if(!bz[i]) pr[++pr[0]]=i,phi[i]=i-1;
		for(int j=1;j<=pr[0]&&i*pr[j]<=n;++j)
		{
			bz[i*pr[j]]=1;
			if(i%pr[j]==0) {phi[i*pr[j]]=phi[i]*pr[j];break;}
			phi[i*pr[j]]=phi[i]*(pr[j]-1);
		}
	}
}
int main()
{
	cin>>n;
	js[0]=1;
	fo(i,1,2*n) js[i]=js[i-1]*(LL)i%mo;
	ny[n]=ksm(js[n],mo-2);
	fod(i,n-1,0) ny[i]=ny[i+1]*(LL)(i+1)%mo; 
	prp();
	int l=sqrt(n);
	LL ans=0;
	fo(i,1,l)
	{
		if(n%i==0)
		{
			ans=(ans+C(2*i,i)*phi[n/i])%mo;
			if(n%(n/i)==0&&(n/i!=i)) ans=(ans+C(2*(n/i),n/i)*phi[i])%mo;
		}
	}
	printf("%lld\n",ans*ksm(2*n,mo-2)%mo);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值