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引理 來解決,那還不如直接上正解。

  • 經過題目轉換,原樹可以轉化成一棵二叉樹(左兒子右兄弟),考慮其括號序列()()()。

  • 對應過來相當於是計算本質不同的 nn00nn11 的序列個數。

  • 可以證明一種 0/1 序列一定是對應一種合法的括號序列的,循環幾次一定合法了。

  • 這個就可以直接套 burnside引理 了。

  • burnside引理 定義:對於一個置換 ff ,若一個染色方案 ss 經過置換後不變,稱 ssff 的不動點。將 ff 的不動點數目記爲 C(f)C(f),則可以證明等價類數目爲所有 C(f)C(f) 的平均值。

  • 先枚舉其置換長度 ii ,則答案爲:i=12nC(i,2n)(i,2n)2\sum_{i=1}^{2n}C^{\frac{(i,2n)}{2}}_{(i,2n)}

  • 其中 (a,b)(a,b) 表示 a,ba,b 的最大公約數。

  • 由於 (i,2n)2\frac{(i,2n)}{2} 要整除,於是 ii 必須爲偶數,轉換枚舉得:i=1nC2(i,n)(i,n)\sum_{i=1}^{n}C^{(i,n)}_{2(i,n)}

  • d=(i,n)d=(i,n) ,則上式=i=1nC2dd=dnφ(nd)C2dd=\sum_{i=1}^{n}C_{2d}^{d}=\sum_{d|n}φ(\frac{n}{d})*C_{2d}^{d}

  • 因爲 (nd,id)=1(\frac{n}{d},\frac{i}{d})=1 ,相當於是 nd\frac{n}{d} 的約數的都會被統計到,於是係數即爲 φ(nd)φ(\frac{n}{d})

  • 所以我們先預處理階乘、逆元,線篩出 φφ

  • 之後我們直接枚舉 dd ,計算即可,時間複雜度 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章