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

版权声明:本文为博主原创文章,未经博主允许必须转载。 https://blog.csdn.net/qq_35950004/article/details/88827515

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

上面的图中不包含根节点的左右子树都是一个节点的二叉树,因为这样的树在一次操作后就会变成上面的图中最右边的那棵树,我们认为这两棵树是同样的,只计算一次.
【样例输入2】
6
【样例输出2】
80
【样例输入3】
20
【样例输出3】
451434801
【样例输入4】
900000
【样例输出4】
255023975
【数据规模和约定】
n < = 1 0 6 n<=10^6

从二叉树和括号序列都能推出Catalan数的O(n2)递推式.寻找这两个递推式的共同之处,我们类比出这样的递归定义:
空串代表空树.一对括号()代表只有一个节点的一棵二叉树.
第一个左括号到第一个右括号之间的部分描述根节点的左子树.剩下的部分描述根节点的右子树.
如果某一对匹配的括号满足不存在其他匹配的括号包含它们,我们就称其为”顶级括号”.例如这个串中红色的是顶级括号:( ( ( ) ( ) ) ) ( ( ( ) ) ( ) ) ( ) ( ( ) )
从二叉树的根节点出发向右儿子方向一直走到没有右儿子时,经过的一条链我们不妨称作”右侧链”.根据刚刚的定义,每个顶级括号都对应右侧链上的一个节点.而每个顶级括号内部的串对应这个节点的左子树.
那么我们能否对括号序列直接Burnside?这里有一个坑.()()()循环移动一次之后会得到)()()(,显然这不能按我们刚才的方法对应到一棵二叉树.
如果选手脑子不太好使,他看到题解这里可能会想要直接用catalan数做Burnside.不就是括号序列,循环移动后相同则本质相同吗?
注意Burnside的条件是什么.我们要有置换群.” 循环移动后相同”对应的置换群就是”循环移动0,1,2,….n-1次”.同时还要有统计的元素.这里我们统计的元素就是括号序列.一个原先的元素在乘上置换群中的任何一个置换之后都必须仍然是一个合法的元素.()()()乘上”循环移动1次”之后变成的)()()(并不是一个合法的元素.
正确的算法是:观察规律可得,打表之后的答案只需要我们利用组合数进行Burnside.统计”n个0,n个1的序列在循环移位后相同则本质相同时有多少个等价类”.这个题是裸的Burnside,预处理一下阶乘和逆元,就可以O(n)了.交上去,AC!
选手:这规律鬼能看出来?oeis题
实际上满分算法是有理有据的.我们只需要证明”n个0,n个1的序列,循环移位后相同则本质相同”的等价类和”n对括号合法的括号序列”的等价类一一对应.
显然,”合法的括号序列”的一个等价类一定唯一对应01序列的一个等价类。而我们要证反过来也成立,01序列的一个等价类一定唯一对应合法的括号序列的一个等价类.实际上就是要证任何一个01序列都可以经过若干次循环移位后对应一个”任意前缀中0的个数不少于1的个数”的序列,然后0对应左括号,1对应右括号,对应出一个括号序列,然后等价类也可以对应了.
然后就有理有据的做完啦!
后记:不知道选手有没有感觉这个T3很扯淡呢?liu_runda出的时候是先出出来了括号序列上的题目描述,然后为了方便选手写O(n3)暴力就魔改成了这样.

二叉树循环同构计数 - > 括号序列循环同构计数 -> 01序列循环同构计数->burnside引理。

粘标程:

#include<cstdio>
#include<cstdlib>
const int maxn=3000005,mod=998244353;
int fac[maxn],inv[maxn],phi[maxn],prime[maxn],tot;
bool flag[maxn];
void init(){
	inv[1]=inv[0]=1;
	for(int i=2;i<maxn;++i)inv[i]=inv[mod%i]*1ll*(mod-mod/i)%mod;
	for(int i=2;i<maxn;++i)inv[i]=inv[i-1]*1ll*inv[i]%mod;
	fac[0]=1;
	for(int i=1;i<maxn;++i)fac[i]=fac[i-1]*1ll*i%mod;
	phi[1]=1;
	for(int i=2;i<maxn;++i){
		if(!flag[i]){
			phi[i]=i-1;prime[++tot]=i;
		}
		for(int j=1;j<=tot&&i*1ll*prime[j]<maxn;++j){
			flag[i*prime[j]]=true;
			if(i%prime[j]==0){
				phi[i*prime[j]]=phi[i]*prime[j];
			}else{
				phi[i*prime[j]]=phi[i]*(prime[j]-1);
			}
		}
	}
}
int C(int n,int m){
	return fac[n]*1ll*inv[m]%mod*inv[n-m]%mod;
}
int Catalan(int n,int m){
	return (C(n+m,m)-C(n+m,m-1)+mod)%mod;
}
int qpow(int a,int x){
	int ans=1;
	for(;x;x>>=1,a=a*1ll*a%mod)if(x&1)ans=ans*1ll*a%mod;
	return ans;
}
int calc(int i,int n){
	return C(2*i,i)*1ll*phi[n/i]%mod;
}
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout); 
	init();
	int n;
	scanf("%d",&n);
	int ans=0;
	for(int i=1;i*i<=n;++i){
		if(n%i!=0)continue;
		ans=(ans+calc(i,n))%mod;
		if(i*i!=n)ans=(ans+calc(n/i,n))%mod;
	}
	ans=ans*1ll*qpow(n*2,mod-2)%mod;
	printf("%d\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_35950004/article/details/88827515