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引理 來解決,那還不如直接上正解。
-
經過題目轉換,原樹可以轉化成一棵二叉樹(左兒子右兄弟),考慮其括號序列()()()。
-
對應過來相當於是計算本質不同的 個 、 個 的序列個數。
-
可以證明一種 0/1 序列一定是對應一種合法的括號序列的,循環幾次一定合法了。
-
這個就可以直接套 burnside引理 了。
-
burnside引理 定義:對於一個置換 ,若一個染色方案 經過置換後不變,稱 爲 的不動點。將 的不動點數目記爲 ,則可以證明等價類數目爲所有 的平均值。
-
先枚舉其置換長度 ,則答案爲:
-
其中 表示 的最大公約數。
-
由於 要整除,於是 必須爲偶數,轉換枚舉得:
-
令 ,則上式
-
因爲 ,相當於是 的約數的都會被統計到,於是係數即爲 。
-
所以我們先預處理階乘、逆元,線篩出 。
-
之後我們直接枚舉 ,計算即可,時間複雜度 。
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;
}