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
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=1∑2nC(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=1∑nC2(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=1∑nC2dd=d∣n∑φ(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;
}