【學習筆記】Burnside引理和Polya定理


reference:

https://blog.csdn.net/xym_CSDN/article/details/53456447
https://blog.csdn.net/thchuan2001/article/details/65641566
https://blog.csdn.net/ta201314/article/details/51050846
https://www.cnblogs.com/pks-t/p/9508866.html
https://www.cnblogs.com/yyf0309/p/Burnside.html
https://oi-wiki.org/math/permutation-group

本文主要介紹了和 Burnside 引理有關的一些簡單的群論知識。

本文的目的僅是引入和簡單證明 Burnside 引理和 Pólya 定理,因此關於群論的部分概念的介紹會比較簡略和不太規范,因為大部分是我到處在網上找資料學的,如果有不當之處,歡迎指出

1 群

1.1 群的定義

若集合 \(S\neq\varnothing\)\(S\) 上的運算 \(\cdot\) 構成的代數結構 \((S,\cdot)\) 滿足以下性質:

  • 封閉性: \(\forall a,b\in S,a\cdot b\in S\)

  • 結合律: \(\forall a,b,c\in S,(a\cdot b)\cdot c=a\cdot(b\cdot c)\)

  • 單位元: \(\exists e\in S,\forall a\in S,e\cdot a=a\cdot e=a\)

  • 逆元: \(\forall a\in S,\exists b\in S,a\cdot b=b\cdot a=e\) ,稱 \(b\)\(a\) 的逆元,記為 \(a^{-1}\)

則稱 \((S,\cdot)\) 為一個(注意到 \(S\) 一定是非空的)。

一些其他定義:

  • 阿貝爾群:即交換群,滿足交換律的群。
  • 半群:由集合 \(S\neq \varnothing\)\(S\) 上的運算 \(\cdot\) 構成的代數結構 \((S,\cdot)\),滿足封閉性和結合律。
  • 有限群:元素個數有限的群稱為有限群,而有限群的元素個數稱作有限群的
  • :由集合 \(S\neq \varnothing\)\(S\) 上的兩個運算 \(+,\cdot\) 構成的代數結構 \((S,+,\cdot)\),滿足 \((S,+)\) 是阿貝爾群,\((S\setminus \{0\},\cdot)\) 是半群(其中 \(0\)\((S,+)\) 的單位元)。
  • 域:由集合 \(S\neq \varnothing\)\(S\) 上的兩個運算 \(+,\cdot\) 構成的代數結構 \((S,+,\cdot)\),滿足 \((S,+),(S\setminus \{0\},\cdot)\) 是阿貝爾群(其中 \(0\)\((S,+)\) 的單位元)。

1.2 群的簡單性質

關於群,有一些比較簡單的想法:

  • 一個群中的單位元唯一。

    證明:假設有兩個單位元 \(e_1,e_2\),有 \(e_1=e_1e_2=e_2\)

  • 如果 \(a\cdot x=e\),我們稱 \(a\)\(x\) 的左逆元;如果 \(x\cdot b=e\),我們稱 \(b\)\(x\) 的右逆元。
    可以證明,在一個群中,左逆元和右逆元是一樣的。

    證明:不妨設 \(c\cdot a=e\),那么 \(x\cdot a=(c\cdot a)\cdot (x\cdot a)=c\cdot (a\cdot x)\cdot a=c\cdot a=e\),即 \(a\) 也是 \(x\) 的右逆元。

  • 一個群中 \(x\) 的逆元唯一。

    證明:如果有 \(x\) 兩個逆元 \(a,b\),那么我們有 \(a=a\cdot x\cdot b=b\)

  • 群中有消去律存在。即 \(\forall a,b,x\in G, ax=bx \Leftrightarrow a=b\)

    證明:兩邊同乘逆元。

在下面的討論中,我們默認是在有限群上討論。

1.3 子群及其衍生

子群:對於一個群 \(G(S,\cdot)\),若 \(T\subseteq S\),且 \(H(T,\cdot)\) 也是一個群,那么稱 \((T,\cdot)\)\((S,\cdot)\) 的一個子群,記為 \(H\leq S\)

生成子群:對於 \(S\) 的一個非空子集 \(T\),我們求出 \(G\) 的所有使 \(T \subseteq T'\) 的子群 \((T',\cdot)\) 的交 \(G'\)\(G'\) 叫做 \(T\)生成子群,同時 \(T\) 也是 \(G'\)生成集合,記為 \(\langle T\rangle\)。當 \(T=\{x\}\),我們也寫作 \(\langle x\rangle\)

循環群:可由一個元素生成的群。

陪集:對於群 \(G\) 的一個子群 \(H\)

  • 如果 \(H \leq G\),對於 \(a\in G\),定義 \(H\) 的一個左陪集\(_aH=\{ah\arrowvert h\in H\}\)
  • 如果 \(H\leq G\),對於 \(a\in G\),定義 \(H\) 的一個右陪集\(H_a=\{ha\arrowvert h\in H\}\)

注意陪集不一定是一個群,因為陪集顯然可能沒有單位元。

1.3.1 陪集的性質

陪集有一些重要的性質,我們下面只討論右陪集的情況(左陪集同理):

  • \(\forall a\in G,|H|=|H_a|\)

    證明:如果 \(h_1\neq h_2\in H\),那么 \(h_1a\neq h_2a\)。對於不同的 \(h\)\(ha\) 互不相同,因此 \(|H|=|H_a|\)

  • \(\forall a\in G,a\in H_a\)

    證明:因為 \(H\) 是群,所以 \(e\in H\),所以 \(ea\in H_a\)\(a\in H_a\)

  • \(H_a=H \Leftrightarrow a\in H\)

    證明:從左推到右,因為 \(a\in H_a\)。從右推到左,由群的封閉性 \(H_a \subseteq H\),而 \(|H|=|H_a|\),所以 \(H_a=H\)

  • \(H_a=H_b \Leftrightarrow ab^{-1}\in H\)
    注意這個性質的右邊也可以寫成 \(a\in H_b\)\(b\in H_a\)\(a^{-1}b\in H\)

    證明:從左推到右,\(a\in H_a\Rightarrow a\in H_b\Rightarrow ab^{-1}\in H\)。從右推到左,\(H_{ba^{-1}}=H\),故 \(H_a=H_b\)

  • \(H_a\cap H_b\neq \varnothing \Rightarrow H_a=H_b\)
    這句話的意思是 \(H\) 的任意兩個陪集要么相等,要么沒有交集。

    證明:考慮 \(c\in H_a\cap H_b\),那么 \(\exist h_1,h_2\in H,h_1a=h_2b=c\),那么 \(ab^{-1}=h_1^{-1}h_2\in H\),故 \(H_a=H_b\)

1.3.2 拉格朗日定理

\(H \leq G\),那么 \(|H|\) 整除 \(|G|\)。更准確地

\[|G|=|H|\cdot [G:H] \]

其中 \([G:H]\) 表示 \(G\)\(H\) 不同的陪集數。

證明:根據陪集的性質,\(H\) 的所有陪集大小相等且互不相交。

1.3.3 一些推論和應用

對於某個元素 \(a\in G\),我們稱 \(a\)周期 \(o(a)=\min\{x\arrowvert a^x=e,x\in\mathbb N^*\}\),在有限群內這個周期一定存在,否則我們令 \(o(a)=+\infty\)

那么對於有限群 \(G\),有以下推論:

  • 對於 \(a\in G\),有 \(o(a)|\ |G|\)

    證明:\(o(a)=|\langle a\rangle|\),顯然 \(\langle a\rangle \leq G\),由拉格朗日定理可知 \(o(a)|\ |G|\)

  • 對每個 \(a\in G\),都有 \(a^{|G|}=e\)

    證明:由前面的推論顯然。

  • \(|G|\) 為素數,則 \(G\) 是循環群。

    證明:對於 \(a \neq e\),有 \(|\langle a\rangle|\neq 1\) 整除 \(|G|\),也就是 \(|\langle a\rangle|=|G|\),因為 \(\langle a\rangle\leq G\),所以 \(\langle a\rangle=G\)

有一些美妙的應用:

  • 費馬小定理:若 \(p\) 是質數,那么 \(\forall a\not\equiv 0\pmod p,a^{p-1}\equiv 1\pmod p\)

    證明只要考慮群 \((\{1,2,\cdots,p-1\}, \times \bmod p)\)

  • 歐拉定理:若 \(\gcd(a,p)=1\),那么 \(a^{\phi(p)}\equiv 1\pmod p\)

    證明只要考慮群 \((\{x \arrowvert x\in[1,p),\gcd(x,p)=1\}, \times \bmod p)\)

2 置換群

2.1 置換的定義

有限集合到自身的雙射(即一一對應)稱為置換。不可重集合 \(S=\{a_1,a_2,\cdots,a_n\}\) 上的置換可以表示為

\[f=\left(\begin{array}{c} {a_{1}, a_{2}, \dots, a_{n}} \\ {a_{p_{1}}, a_{p_{2}}, \dots, a_{p_{n}}} \end{array}\right) \]

表示將 \(a_i\) 映射為 \(a_{p_i}\),即 \(f(a_i)=a_{p_i}\)。其中 \(p_1,p_2,\cdots,p_n\)\(1 \sim n\) 的一個排列。

如果我們沒有強制 \(a_1,a_2,\cdots,a_n\) 的排列順序,那么顯然這些列的順序是不要緊的。

顯然 \(S\) 上的所有置換的數量為 \(n!\)

2.2 置換的乘法

對於兩個置換,\(f=\left(\begin{array}{l}{a_{p_{1}}, a_{p_{2}}, \dots, a_{p_{n}}} \\{a_{q_{1}}, a_{q_{2}}, \dots, a_{q_{n}}}\end{array}\right)\)\(g=\left(\begin{array}{c}{a_{1}, a_{2}, \ldots, a_{n}} \\{a_{p_{1}}, a_{p_{2}}, \ldots, a_{p_{n}}}\end{array}\right)\)\(f\)\(g\) 的乘積記為 \(f\circ g\),其值為

\[f \circ g=\left(\begin{array}{c} {a_{1}, a_{2}, \ldots, a_{n}} \\ {a_{q_{1}}, a_{q_{2}}, \ldots, a_{q_{n}}} \end{array}\right) \]

\((f\circ g)(x)=f(g(x))\),簡單來說就是先經過了 \(g\) 的映射再經過了 \(f\) 的映射。

2.3 置換群

易證,集合 \(S\) 上的所有置換關於置換的乘法滿足封閉性、結合律、有單位元(恆等置換/單位置換,即每個元素映射成它自己)、有逆元(交換置換表示中的上下兩行),因此構成一個群。

通常我們把在 \(\{1,2,\cdots,n\}\) 上的所有置換構成的群稱為 \(n\) 元對稱群,記為 \(S_n\)

這個群的任意一個子群即稱為置換群

2.4 循環置換

循環置換(也叫輪換)是一類特殊的置換,可表示為

\[\left(a_{1}, a_{2}, \dots, a_{m}\right)=\left(\begin{array}{c} {a_{1}, a_{2}, \dots, a_{m-1}, a_{m}} \\ {a_{2}, a_{3}, \dots, a_{m}, a_{1}} \end{array}\right) \]

若兩個循環置換不含有相同的元素,則稱它們是不相交的。有如下定理:

任意一個置換都可以分解為若干不相交的循環置換的乘積,例如

\[\left(\begin{array}{l} {a_{1}, a_{2}, a_{3}, a_{4}, a_{5}} \\ {a_{3}, a_{1}, a_{2}, a_{5}, a_{4}} \end{array}\right)=\left(a_{1}, a_{3}, a_{2}\right) \circ\left(a_{4}, a_{5}\right) \]

該定理的證明也非常簡單。如果把元素視為圖的節點,映射關系視為有向邊,則每個節點的入度和出度都為 1,因此形成的圖形必定是若干個環的集合,而一個環即可用一個循環置換表示。

2.5 置換的奇偶性

這個蠻提一下,雖然和重點沒啥關系,但是挺有意思的。

我們知道排列有奇偶性,置換也有奇偶性。

排列的奇偶性:

  • 定義排列的奇偶性和排列的逆序對數的奇偶性相同。
  • 我們知道交換排列的兩個相鄰元素會使整個排列的逆序對數的奇偶性取反,而交換當前兩個 \(p_i,p_j\) 可以用 \(2|i-j|-1\) 次交換相鄰元素實現,因此交換任意兩個不同元素也會使整個排列的逆序對數取反。

置換的奇偶性:

  • 我們稱對換為大小為 \(2\)非單位置換(即交換某兩個元素)。定義置換的奇偶性與該置換變成單位置換所需的對換次數的奇偶性相同。
  • 不難發現,對於置換 \(\left(\begin{array}{c} {1,2,\cdots,n} \\ {p_{1}, p_{2}, \cdots, p_{n}} \end{array}\right)\),其奇偶性與排列 \(p_1,p_2,\cdots,p_n\) 的奇偶性相同。
  • 我們發現,大小為 \(n\) 的輪換,可以用 \(n-1\) 次對換變成單位置換,而置換又可以分解為若干不相交的輪換。若一個置換可以分解為 \(d\) 個不相交的輪換,那么這個置換的奇偶性與 \(n-d\) 的奇偶性相同。

3 軌道-穩定子定理

定義 \(A,B\) 是兩個有限集合,\(X=B^A\) 表示所有從 \(A\)\(B\) 的映射,\(G\) 是作用在 \(A\) 上的一個置換群。

(比如給正方體六個面染色,\(A\) 就是正方體六個面的集合,\(B\) 就是所有顏色的集合,\(X\) 就是不考慮本質不同的方案集合,即 \(|X|=|B|^{|A|}\)

我們定義,對於每個 \(x\in X\)

\[G^x=\{g\arrowvert g(x)=x,g\in G\}\\ G(x)=\{g(x) \arrowvert g \in G\} \]

其中 \(G^x\) 稱為 \(x\)穩定子\(G(x)\) 稱為 \(x\)軌道

軌道-穩定子定理:

\[|G|=|G^x|\cdot |G(x)| \]

證明:首先可以證明 \(G^x\)\(G\) 的一個子群,因為

  • 封閉性:若 \(f,g\in G\) ,則 \((f\circ g)(x)=f(g(x))=f(x)=x\) ,所以 \(f\circ g\in G^x\)
  • 結合律:顯然置換的乘法滿足結合律。
  • 單位元:因為 \(I(x)=x\) ,所以 \(I\in G^x\)\(I\) 為恆等置換)。
  • 逆元:若 \(g\in G^x\) ,則 \(g^{-1}(x)=g^{-1}(g(x))=(g^{-1}\circ g)(x)=I(x)=x\) ,所以 \(g^{-1}\in G^x\)

由拉格朗日定理得 \(|G|=|G^x|\cdot[G:G^x]\)。下面只要證明 \(|G(x)|=[G:G^x]\)(直觀理解這是很顯然的,但是我們還是要證明一下)

  • \(\varphi(g(x))=\ _gG^x\),下面證明 \(\varphi\)單射,則 \(|G(x)|\leq [G:G^x]\)
    • \(g(x)=f(x)\) ,兩邊同時左乘 \(f^{-1}\) ,可得 \((f^{-1}\circ g)(x)=I(x)=x\) ,所以 \(f^{-1}\circ g\in G^x\) ,由陪集的性質可得 \(_gG^x=\ _fG^x\)
    • 反過來可證,若 \(_gG^x=\ _fG^x\) ,則有 \(g(x)=f(x)\)
    • 以上兩點說明對於一個 \(g(x)\) ,恰有一個左陪集與其對應;且對於每個左陪集,至多有一個 \(g(x)\) 與之對應。
    • \(\varphi\) 是一個從 \(G(x)\) 到左陪集的單射。
  • \(\varphi'(\ _gG^x)=g(x)\),同理證明 \(\varphi'\)單射,則 \(|G(x)|\geq [G:G^x]\)
    • 證明和上面類似。

4 Burnside 引理

定義 \(A,B\) 是兩個有限集合,\(X=B^A\) 表示所有從 \(A\)\(B\) 的映射,\(G\) 是作用在 \(A\) 上的一個置換群(跟上面一樣)。\(X/G\) 表示作用在 \(X\) 上產生的所有等價類的集合(若 \(X\) 中的兩個映射能經過 \(G\) 中的置換作用后相等,則它們在同一等價類中)。

\(X/G\) 其實就是,對於所有的 \(x\in X\)不同軌道的集合,這些軌道必定是不交的。因此我們也將 \(|X/G|\) 叫做 \(X\) 關於 \(G\)軌道數

Burnside 引理:

\[|X/G|=\frac{1}{|G|}\sum_{g\in G}|X^g| \]

其中 \(X^g=\{x\arrowvert g(x)=x,x\in X\}\),我們稱 \(X^g\)\(X\) 在置換 \(g\) 下的不動點集合。

文字描述:\(X\) 關於置換群 \(G\) 的軌道數,等於 \(G\) 中每個置換下不不動點的個數的算術平均數。

證明:(Burnside 引理本質上是更換了枚舉量,從而方便計數

\[\begin{aligned} |X/G|&=\sum_{Y\in X/G}1\\ &=\sum_{Y\in X/G}\sum_{x\in Y}\frac{1}{|Y|}\\ &=\sum_{Y\in X/G}\sum_{x\in Y}\frac{1}{|G(x)|}\\ &=\sum_{x\in X}\frac{1}{|G(x)|}\\ \end{aligned} \]

根據軌道-穩定子定理,我們有 \(|G|=|G^x|\cdot |G(x)|\),所以

\[\begin{aligned} |X/G|&=\sum_{x\in X}\frac{1}{|G(x)|}\\ &=\frac{1}{|G|}\sum_{x\in X}\frac{|G|}{|G(x)|}\\ &=\frac{1}{|G|}\sum_{x\in X}|G^x|\\ &=\frac{1}{|G|}|\left\{(x,g)\arrowvert g(x)=x,(x,g)\in X\times G\right\}|\\ &=\frac{1}{|G|}\sum_{g\in G}|X^g| \end{aligned} \]

至此我們就證明了 Burnside 引理。

注意當 \(X\subseteq B^A\) 時,Burnside 引理也是成立的。也就是說,我們給 \(A\)\(B\) 的映射加上一些條件,Burnside 引理仍然成立。其原因就是上面的證明沒有用到 \(X=B^A\)

5 Pólya 定理

是 Burnside 引理的一種特殊形式。

前置條件與 Burnside 引理相同,內容修改為

\[|X/G|=\frac{1}{|G|}\sum_{g\in G} |B|^{c(g)} \]

\(c(g)\) 表示置換 \(g\) 拆出的不相交輪換數量。

證明:在 Burnside 引理中,\(g(x)=x\) 的充要條件是 \(x\)\(g\) 中每個輪換內的元素都映射到了 \(B\) 中的同一個元素,所以 \(|X^g|=|B|^{c(g)}\),即可得 Pólya 定理。

注意只有當 \(X=B^A\) 成立時(也就是當 \(X\)\(A\)\(B\) 的所有映射時),Pólya 定理才成立,否則不一定成立。

6 應用

6.1 環上的計數問題

6.1.1 「FJWC2020 Day2」手鏈強化

來源:FJWC2020 Day2T2

給一個有 \(n\) 個珠子的手鏈染色,一共有 \(k\) 種顏色,每個珠子可以選擇染這 \(k\) 種顏色之一,也可以選擇不染色。不能存在兩個相鄰的珠子都被染色。

求有多少種本質不同的染色方案。兩種方案本質相同,當且僅當通過旋轉能相同(不包括翻轉)。

\(10^9+7\) 取模。

\(n,k \leq 10^9\)

時空限制:\(1\texttt s/512\texttt{MB}\)

這題屬於 Burnside 引理的簡單應用。因為是第一個例題,所以講得比較詳細。

首先我們考慮旋轉對環的影響,我們發現旋轉相當於給 \(n\) 個珠子作用上了一個置換,而所有旋轉方式產生的置換群共有 \(n\) 個置換:\((2,3,\cdots,n-1,n,1)^k~(k\in\{0,1,2,\cdots,n-1\})\)

利用 Burnside 引理,所有染色方案關於置換群的軌道數,就等於每個置換下不動點個數的算數平均數。因此我們只要枚舉每個置換,計算一下每種置換下的不動染色方案數。

我們可以將每個置換看成順時針旋轉 \(k\) 個位置,要求在這個置換下不動的染色方案數,就相當於限制,對於每個 \(i\),都有 \(c_i=(c_i+k-1)\bmod n+1\),其中 \(c_i\) 表示在不旋轉的情況下,第 \(i\) 個珠子的染色情況。

我們不妨將強制相同的珠子並成一個集合,那么相當於每個集合都強制染同一種顏色。等價的說法是,將這個置換分解為輪換,屬於同一個輪換的珠子需要染成同一個顏色,不屬於同一個輪換的就沒有相交部分,沒有這樣的限制。

用線性同余方程的理論可以證明,這樣操作之后,共有 \(\gcd(k,n)\) 個集合,每個集合有 \(\frac{n}{\gcd(k,n)}\) 個珠子,其中第 \(i\) 個珠子就屬於第 \((i-1)\bmod \gcd(k,n)+1\) 個集合。

並且第 \(i\) 個集合里面每個珠子的相鄰兩個珠子屬於的集合就是 \(i-1\)\(i+1\)。特別地,\(1\)\(\gcd(k,n)\) 這兩個集合也是相鄰的。因此對相鄰珠子染色的限制,現在可以看成對相鄰集合染色的限制。而且我們可以簡單地將所有集合看成一個大小為 \(\gcd(k,n)\) 的環

我們記 \(f(d)\) 表示給大小為 \(d\) 的環染色的方案數(此時不考慮旋轉同構)。那么答案就是

\[\begin{aligned} \frac{1}{n}\sum_{i=1}^nf\left(\gcd(i,n)\right)&=\frac{1}{n}\sum_{d|n}f(d)\sum_{i=1}^n[\gcd(i,n)=d]\\ &=\frac{1}{n}\sum_{d|n}f(d)\sum_{i=1}^{\frac{n}{d}}[\gcd(i,\frac{n}{d})=1]\\ &=\frac{1}{n}\sum_{d|n}f(d)\varphi(\frac{n}{d}) \end{aligned} \]

\(f(d)\) 就是一件很簡單的事情了。先考慮給鏈染色(先不考慮首尾相連的問題),設 \(g(i,0/1)\) 表示考慮了前 \(i\) 個珠子,最后一個是否染色的方案數。那么有

\[\begin{aligned} g(i,0)&=g(i-1,0)+g(i-1,1)\\ g(i,1)&=g(i-1,0)\cdot k \end{aligned} \]

只要枚舉第一個珠子是否染色。不染色的方案數是 \(g(i-1,0)+g(i-1,1)\),染色的方案數是 \(g(i-2,0)\cdot k\)

因此 \(f(i)=g(i-1,0)+g(i-1,1)+k\cdot g(i-2,0)\)

提前將 \(n\) 質因數分解,然后用 dfs 枚舉約數 \(d\),順便在 dfs 的過程中求出 \(\varphi (d)\),用矩陣快速冪優化遞推來求 \(g\)

時間復雜度 \(\mathcal O(\sqrt n+\sigma_0(n)\log n)\)

#include <bits/stdc++.h>

const int mod = 1e9 + 7; 
const int MaxN = 1e6 + 5; 

inline void add(int &x, const int &y)
{
	x += y;
	if (x >= mod)
		x -= mod; 
}

struct matrix
{
	int r, c; 
	int mat[3][3]; 

	matrix(){}
	matrix(int _r, int _c):
		r(_r), c(_c) {memset(mat, 0, sizeof(mat));}

	inline void init()
	{
		for (int i = 1; i <= r; ++i)
			mat[i][i] = 1; 
	}

	inline matrix operator * (const matrix &rhs) const
	{
		matrix res(r, rhs.c); 
		for (int i = 1; i <= r; ++i)
			for (int k = 1; k <= c; ++k)
				for (int j = 1; j <= rhs.c; ++j)
					res.mat[i][j] = (res.mat[i][j] + 1LL * mat[i][k] * rhs.mat[k][j]) % mod; 
		return res; 
	}

	inline matrix operator ^ (int p) const
	{
		matrix res(r, c), x = *this; 
		res.init(); 

		for (; p; p >>= 1, x = x * x)
			if (p & 1)
				res = res * x; 
		return res; 
	}
}T(2, 2), F0(2, 1); 

int n, K; 
int cnt, p[MaxN], c[MaxN]; 

int ans; 

inline int qpow(int x, int y)
{
	int res = 1; 
	for (; y; y >>= 1, x = 1LL * x * x % mod)
		if (y & 1)
			res = 1LL * res * x % mod; 
	return res; 
}

inline int F(int n)
{
	if (n == 1)
		return 1; 

	matrix F2 = (T ^ (n - 2)) * F0; 
	matrix F1 = T * F2; 

	int res = 1LL * F2.mat[1][1] * K % mod; 
	add(res, F1.mat[1][1]); 
	add(res, F1.mat[2][1]); 
    
	return res; 
}

inline void dfs(int t, int cur = 1, int phi = 1)
{
	if (t > cnt)
	{
		add(ans, 1LL * F(cur) * phi % mod); 
		return; 
	}

	int cur_phi = p[t] - 1, pri = p[t], nxt = 1; 
	for (int i = 2; i <= c[t]; ++i)
		cur_phi *= p[t]; 

	for (int i = 0; i <= c[t]; ++i)
	{
		dfs(t + 1, cur * nxt, phi * cur_phi); 
		
		if (i == c[t] - 1)
			cur_phi = 1; 
		else
			cur_phi /= pri; 
		nxt *= pri; 
	}
}

int main()
{
	freopen("bracelet.in", "r", stdin); 
	freopen("bracelet.out", "w", stdout); 

	std::cin >> n >> K; 

	T.mat[1][1] = T.mat[1][2] = F0.mat[1][1] = 1; 
	T.mat[2][1] = K; 

	int x = n; 
	for (int i = 2; i * i <= n; ++i)
		if (x % i == 0)
		{
			p[++cnt] = i; 
			while (x % i == 0)
				x /= i, ++c[cnt]; 
		}

	if (x > 1)
	{
		++cnt; 
		p[cnt] = x; 
		c[cnt] = 1; 
	}

	dfs(1); 
	ans = 1LL * ans * qpow(n, mod - 2) % mod; 

	std::cout << ans << std::endl; 

	return 0; 
}

6.1.2 「MtOI2018」魔力環

Luogu #4196 LibreOJ #6519

你需要用 \(m\) 個黑珠子和 \(n-m\) 個白珠子串成一個環,使得這個環上不會出現一段連續的黑珠子,其長度超過 \(k\),求通過旋轉環不會相同的方案數 \(\bmod 998244353\)\(m \leq n \leq 10^5\)\(k \leq 10^5\)

時空限制:\(1\texttt s/512\texttt{MB}\)

首先利用 Burnside 引理的套路,答案就是

\[\frac{1}{n}\sum_{d|n}\left[m\middle|\frac{n}{d}\right]f\left(d,\frac{m}{\frac{n}{d}}\right)\varphi(\frac{n}{d}) \]

其中 \(f(n,m)\) 表示用 \(m\) 個黑珠子和 \(n-m\) 個白珠子串成一個環的方案數(不考慮旋轉同構)。
特別地我們需要特判 \(m=n\) 的情況。

考慮斷環為鏈,首先特判掉 \(m \leq k\) 的情況,此時 \(f(n,m)=\binom nm\)

否則我們考慮枚舉和 \(1\)\(n\) 之間的分界線相鄰的黑珠子連續段長度 \(i(0 \leq i \leq k)\),這樣有 \(i+1\) 種擺放方式,然后強制這個連續段的兩段連着白珠子。這樣剩下的部分就可以看成鏈上的問題了。

現在等價於將 \(m-i\) 個黑珠子插進 \(n-m\) 個白珠子間的縫隙中,等價於求將 \(m-i\) 個黑珠子划分為 \(n-m-1\) 段的方案數(一段可以為空),並且每段的長度不能超過 \(k\)

那么我們設 \(g(n,m)\) 表示將 \(n\) 個珠子分成 \(m\) 段,每段不能超過 \(k\) 的方案數。可以容斥計算,強制一些段超過 \(k\)

\[g(n,m)=\sum_{i=0}^{\min(m,\lfloor\frac{n}{k+1}\rfloor)}(-1)^i\binom m i\binom{n-m(i+1)+m-1}{m-1} \]

那么

\[f(n,m)=\sum_{i=0}^kg(m-i,n-m-1)~(m >k) \]

枚舉約數 \(d\) 之后,計算 \(g(n,m)\) 的時間復雜度是 \(\mathcal O(\frac n k)\),那么計算 \(f(d,*)\) 的時間復雜度就是 \(\mathcal O(d)\)

因此總的時間復雜度就是 \(\mathcal O(\sigma(n))\),即 \(n\) 的約數和。

#include <bits/stdc++.h>

const int mod = 998244353; 

inline void add(int &x, const int &y)
{
	x += y; 
	if (x >= mod)
		x -= mod; 
}

inline void dec(int &x, const int &y)
{
	x -= y; 
	if (x < 0)
		x += mod; 
}

const int MaxN = 2e5 + 5; 

int n, m, K; 
int phi[MaxN]; 
int fac[MaxN], ind[MaxN], fac_inv[MaxN]; 

inline int C(int n, int m)
{
	if (n < m || n < 0 || m < 0) return 0; 
	return 1LL * fac[n] * fac_inv[m] % mod * fac_inv[n - m] % mod; 
}

inline void fac_init(int n)
{
	fac[0] = fac_inv[0] = ind[1] = 1; 
	for (int i = 1; i <= n; ++i)
	{
		if (i > 1)
			ind[i] = 1LL * ind[mod % i] * (mod - mod / i) % mod; 
		fac[i] = 1LL * fac[i - 1] * i % mod; 
		fac_inv[i] = 1LL * fac_inv[i - 1] * ind[i] % mod; 
	}
}

inline void sieve_init(int n)
{
	static bool sie[MaxN]; 
	static int pri[MaxN], cnt; 
	phi[1] = 1; 
	for (int i = 2; i <= n; ++i)
	{
		if (!sie[i])
		{
			pri[++cnt] = i; 
			phi[i] = i - 1; 
		}
		for (int j = 1; j <= cnt && 1LL * pri[j] * i <= n; ++j)
		{
			int x = pri[j] * i; 
			sie[x] = true; 
			if (i % pri[j] == 0)
			{
				phi[x] = phi[i] * pri[j]; 
				break; 
			}
			else
				phi[x] = phi[i] * (pri[j] - 1); 
		}
	}
}

inline int g(int n, int m)
{
	int res = 0, lim = std::min(m / (K + 1), n); 
	for (int i = 0; i <= lim; ++i)
	{
		int cur = 1LL * C(n, i) * C(m - i * (K + 1) + n - 1, n - 1) % mod; 
		if (i & 1)
			dec(res, cur); 
		else
			add(res, cur); 
	}
	return res; 
}

inline int f(int n, int m)
{
	if (K >= m)
		return C(n, m); 
	else if (n == m + 1)
		return 0; 

	int res = 0; 
	for (int i = 0, t = n - m - 1; i <= K; ++i)
		res = (res + 1LL * (i + 1) * g(t, m - i)) % mod; 
	return res; 
}

int main()
{
	std::cin >> n >> m >> K; 

	if (n == m)
		return puts(K >= n ? "1" : "0"), 0; 

	sieve_init(n); 
	fac_init(n << 1); 

	int ans = 0; 
	for (int i = 1; i <= n; ++i)
		if (n % i == 0 && m % (n / i) == 0)
			add(ans, 1LL * phi[n / i] * f(i, m / (n / i)) % mod); 
	std::cout << 1LL * ans * ind[n] % mod << std::endl; 

	return 0; 
}

本題還有另外一個版本:

不限制總的黑珠子的個數,即一共有 \(n\) 個珠子,每個珠子可以染成黑色或白色,要求不存在一段連續的黑珠子長度超過 \(k\)\(1 \leq k \leq n \leq 10^7\)

同樣答案是

\[\frac{1}{n}\sum_{d|n}f(d)\varphi(\frac{n}{d}) \]

其中 \(f(d)\) 表示給長為 \(d\) 的環染色的方案數(不考慮旋轉同構)。同樣需要特判 \(n=k\)

和上面類似的思路,枚舉 \(1,n\) 交界處相鄰的黑色連續段長度,然后斷環為鏈,不過這個時候的鏈就沒有限制黑珠子的總數了,可以任意染色,我們記 \(g(n)\) 表示給長度為 \(n\) 的鏈染色的方案數。

那么用容斥,我們有

\[g(n)=2g(n-1)-\begin{cases}0 & n\leq k\\1 & n=k+1\\g(n-k-2)&n>k+1\end{cases} \]

含義就是用 \(2g(n-1)\) 減去末尾恰好 \(k+1\) 個黑珠子的方案數(超過 \(k+1\) 個的前面已經扣掉了)。

這樣的時間復雜度仍然是 \(\mathcal O(\sigma(n))\)

不過計算 \(f\) 的過程可以用前綴和優化,那么時間復雜度就可以優化到 \(\mathcal O(n)\) 了。

#include <bits/stdc++.h>

const int mod = 1e8 + 7; 

inline void add(int &x, const int &y)
{
	x += y; 
	if (x >= mod)
		x -= mod; 
}

inline void dec(int &x, const int &y)
{
	x -= y; 
	if (x < 0)
		x += mod; 
}

inline int qpow(int x, int y)
{
	int res = 1; 
	for (; y; y >>= 1, x = 1LL * x * x % mod)
		if (y & 1)
			res = 1LL * res * x % mod; 
	return res; 
}

const int MaxN = 1e7 + 5; 

int n, K; 
int phi[MaxN], f[MaxN]; 

inline void sieve_init(int n)
{
	static bool sie[MaxN]; 
	static int pri[MaxN], cnt; 
	phi[1] = 1; 
	for (int i = 2; i <= n; ++i)
	{
		if (!sie[i])
		{
			pri[++cnt] = i; 
			phi[i] = i - 1; 
		}
		for (int j = 1; j <= cnt && 1LL * pri[j] * i <= n; ++j)
		{
			int x = pri[j] * i; 
			sie[x] = true; 
			if (i % pri[j] == 0)
			{
				phi[x] = phi[i] * pri[j]; 
				break; 
			}
			else
				phi[x] = phi[i] * (pri[j] - 1); 
		}
	}
}

bool flg; 
int pw[MaxN]; 

inline int calc(int n)
{
	if (flg)
		return pw[n]; 

	int res = 0; 
	for (int i = 0; i <= K && i < n - 1; ++i)
		res = (res + 1LL * (i + 1) * f[n - i - 2]) % mod; 
	if (n - 1 <= K)
		add(res, n); 
	return res; 
}

int main()
{
	freopen("loop.in", "r", stdin); 
	freopen("loop.out", "w", stdout); 

	std::cin >> n >> K; 
	flg = n == K; 
	sieve_init(n); 

	f[0] = pw[0] = 1; 
	for (int i = 1; i <= n; ++i)
	{
		add(f[i] = f[i - 1], f[i - 1]); 
		if (i >= K + 2)
			dec(f[i], f[i - K - 2]); 
		else if (i == K + 1)
			dec(f[i], 1); 

		pw[i] = pw[i - 1] * 2LL % mod; 
	}

	int ans = 0; 
	for (int i = 1; i <= n; ++i)
		if (n % i == 0)
			add(ans, 1LL * phi[n / i] * calc(i) % mod); 

	std::cout << 1LL * ans * qpow(n, mod - 2) % mod << std::endl; 

	return 0; 
}

6.1.3 「SDOI2013」項鏈

Luogu #3307

項鏈由 \(n\) 個珠子串成一個環。

每個珠子是一個正三棱柱,正三棱柱的三個側面刻有數字,每個數字 \(x\) 需要滿足 \(1 \leq x \leq a\),並且珠子上的三個數字的 \(\gcd=1\)。兩個珠子相同,當且僅當三棱柱通過旋轉或翻轉能相同(對應面的數字相同)。

項鏈的相鄰的兩個珠子必須不同。兩個項鏈如果能通過旋轉變成一樣的,那么認為兩個項鏈相同。

求有多少種不同的項鏈,對 \(10^9+7\) 取模。共有 \(T\) 組數據。

\(n \leq 10^{14}\)\(a \leq 10^7\)\(T \leq 10\)

時空限制:\(3\texttt s/128\texttt{MB}\)

首先我們先計算珠子有多少種。考慮 Burnside 引理,珠子的旋轉和翻轉形成的置換群一共有 \(6\) 個置換。每個置換下的不動點數,就是將置換分解為 \(k\) 個輪換后,最大公約數為 \(1\)有序 \(k\) 元組的數量。

\(S_k\) 表示最大公約數為 \(1\)有序 \(k\) 元組的數量。那么不同的珠子數 \(m\) 就是

\[m=\frac{2S_1+3S_2+S_3}{6} \]

通過莫比烏斯反演,我們可以算出 \(S_k=\sum\limits_{i=1}^a\mu(i){\lfloor\frac ai\rfloor}^k\),具體推導略,用整除分塊算即可。

接着再用 Burnside 引理算不同項鏈數,根據套路,答案就是

\[\frac{1}{n}\sum_{d|n}f(d)\varphi(\frac{n}{d}) \]

其中 \(f(d)\) 表示 \(d\) 個珠子的環的方案數(不考慮旋轉同構)。

這樣算需要面臨的問題是 \(n\) 可能是 \(p=10^9+7\) 的倍數。但是因為 \(n\) 還不可能是 \(p^2\) 的倍數,所以這個時候我們將模數設成 \(p^2\)。有除法的地方只有最后這里,因為 \(n=kp\),所以 \(\sum_{d|n}f(d)\varphi(\frac{n}{d})\) 一定有 \(p\) 這個因子,在模 \(p^2\) 意義下一定還是有 \(p\) 這個因子。因此最后算出來只要將模數和答案同除以 \(p\),然后乘上 \(k\) 在模 \(p\) 意義下的逆元即可。

接下來我們只考慮求 \(f(n)\)。考慮插入第 \(n\) 個珠子,若原來的 \(1\)\(n-1\) 顏色不同,那么再插入一個就是 \((m-2)f(n-1)\)。如果 \(1\)\(n-1\) 相同,那么就是 \((m-1)f(n-2)\)

所以 \(f(n)=(m-2)f(n-1)+(m-1)f(n-2)\)。為了使后面的遞推正確,令 \(f(0)=m,f(1)=0\)。到這其實可以矩乘優化了,但是發現這是二階常系數線性齊次遞推式,其實可以推導出通項公式。

reference:https://blog.csdn.net/bzjr_Log_x/article/details/104225410

具體地,我們用特征根法,解出方程 \(\lambda^2=(m-2)\lambda+m-1\) 的兩個特征根 \(\lambda_1=-1,\lambda_2=m-1\)

那么 \(f(n)=A\lambda_1^n+B\lambda_2^n\),代入解得 \(f(n)=(-1)^n(m-1)+(m-1)^n\)

時間復雜度 \(\mathcal O(T\sigma_0(n)\log n+T\sqrt n)\)

#include <bits/stdc++.h>

template <class T>
inline void relax(T &x, const T &y)
{
	if (x < y) x = y; 
}

typedef long long s64; 
typedef long double ld; 

const s64 mod1 = 1e9 + 7; 
const s64 mod2 = mod1 * mod1; 
const s64 inv6_1 = (mod1 + 1) / 6; 
const s64 inv6_2 = 833333345000000041LL; 

s64 mod, inv6; 

inline void add(s64 &x, const s64 &y)
{
	x += y; 
	if (x >= mod)
		x -= mod; 
}

inline void dec(s64 &x, const s64 &y)
{
	x -= y;
	if (x < 0)
		x += mod; 
}

inline s64 qmul(s64 x, s64 y)
{
	if (mod == mod1)
		return 1LL * x * y % mod; 
	else
	{
		s64 res = x * y - (s64)((ld)x * y / mod + 1e-14) * mod;
		return res < 0 ? res + mod : res;
	}
}

inline s64 qpow(s64 x, s64 y)
{
	s64 res = 1; 
	for (; y; y >>= 1, x = qmul(x, x))
		if (y & 1)
			res = qmul(res, x); 
	return res; 
}

const int MaxN = 1e7 + 5; 

int n_pri, pri[MaxN], miu[MaxN]; 

inline void sieve_init(int n)
{
	miu[1] = 1; 

	static bool sie[MaxN]; 
	for (int i = 2; i <= n; ++i)
	{
		if (!sie[i])
		{
			pri[++n_pri] = i; 
			miu[i] = -1;  
		}
		for (int j = 1; j <= n_pri && i * pri[j] <= n; ++j)
		{
			int x = i * pri[j]; 
			sie[x] = true; 

			if (i % pri[j] == 0)
			{
				miu[x] = 0; 
				break; 
			}
			else
				miu[x] = -miu[i]; 
		}
	}
	for (int i = 1; i <= n; ++i)
		miu[i] += miu[i - 1]; 
}

int a; 
s64 n, ans, m, p[MaxN]; 
int cnt, c[MaxN]; 

inline s64 f(s64 n)
{
	s64 res = qpow(m, n); 
	return (n & 1 ? dec(res, m) : add(res, m)), res; 
}

inline void dfs(int k, s64 d = 1, s64 phi = 1)
{
	if (k > cnt)
	{
		add(ans, qmul(f(d), phi % mod)); 
		return; 
	}

	s64 prod = 1; 
	for (int i = 1; i <= c[k]; ++i)
		prod *= p[k]; 

	dfs(k + 1, d * prod, phi); 
	s64 cur = 1; 
	for (int i = 1; i <= c[k]; ++i)
	{
		prod /= p[k], cur *= p[k] - (i == 1); 
		dfs(k + 1, d * prod, cur * phi); 
	}
}

inline void case_answer(s64 cx_n, int cx_a)
{
	n = cx_n, a = cx_a; 
	if (n % mod1 == 0)
		mod = mod2, inv6 = inv6_2; 
	else
		mod = mod1, inv6 = inv6_1; 
	
	m = 2; 
	for (int i = 1, nxt; i <= a; i = nxt + 1)
	{
		int d = a / i; 
		nxt = a / (a / i); 
		s64 delta = (miu[nxt] - miu[i - 1] + mod) % mod; 
		s64 val = (3LL * d * d % mod + qmul(1LL * d * d % mod, d)) % mod; 
		
		add(m, qmul(val, delta)); 
	}
	m = qmul(inv6, m); 
	--m; 

	s64 x = n; cnt = 0; 
	for (int j = 1, i; i = pri[j], j <= n_pri && 1LL * i * i <= n && x > 1; ++j)
		if (x % i == 0)
		{
			p[++cnt] = i, c[cnt] = 0; 
			while (x % i == 0)
				++c[cnt], x /= i; 
		}
	if (x > 1) ++cnt, c[cnt] = 1, p[cnt] = x; 

	ans = 0; 
	dfs(1); 

	if (mod == mod1)
		std::cout << 1LL * ans * qpow(n % mod, mod - 2) % mod << '\n'; 
	else
	{
		ans /= mod1, n /= mod1; 
		mod = mod1; 
		std::cout << 1LL * ans * qpow(n % mod, mod - 2) % mod << '\n'; 
	}
}

int main()
{
	int orzcx; 
	static s64 cx_n[11]; 
	static int cx_a[11], max_a; 

	scanf("%d", &orzcx); 
	for (int i = 1; i <= orzcx; ++i)
		scanf("%lld%d", &cx_n[i], &cx_a[i]), relax(max_a, std::max((int)sqrt(cx_n[i]), cx_a[i])); 

	sieve_init(max_a); 
	for (int i = 1; i <= orzcx; ++i)
		case_answer(cx_n[i], cx_a[i]); 

	return 0; 
}

6.1.4 「Celeste-B」Say Goodbye

Luogu #5564

給定 \(n\) 個珠子和 \(m\) 種顏色,第 \(i\) 種顏色的珠子恰有 \(a_i\) 種。保證 \(\sum a_i=n\)

將這 \(n\) 個珠子串成一個環長 \(\geq 2\) 的無向基環樹。基環樹的兩個子樹不同當且僅當它們對應點的顏色不同或者這兩棵子樹不同構,兩棵子樹同構當且僅當,兩個根的每個兒子的子樹對應同構和一般的樹的計數不同,這里的兒子是有順序的。

例如下面的幾種部分串法是互不相同的

如果兩個基環樹能通過旋轉基環得到同樣的結果,那么這兩種基環樹本質上是相同的。求有多少本質不同的基環樹。

\(m \leq n \leq 2\times 10^5\)

時空限制:\(1\texttt{s}/512\texttt{MB}\)

reference:https://www.cnblogs.com/PinkRabbit/p/11525881.html

首先考慮不染色的情況下的 \(n\) 個點的無標號有根有序樹(兒子有序)的計數。考慮這棵樹的括號序列,因為最外層必須只有一對括號,所以方案數就是 \(\mathrm{Cat}_{n-1}\),其中 \(\mathrm{Cat}\) 是卡特蘭數。

那么環長為 \(k\) 的基環樹相當於 \(k\) 棵大小之和為 \(n\) 的無標號有根有序樹。我們枚舉環長,然后用 Burnside 引理計算答案,為了后續推導方便,寫成

\[\mathrm{Ans}=\sum_{k=2}^n\frac{1}{k}\sum_{d|k}f_k(\frac{k}{d})\varphi(d) \]

其中 \(f_k(d)\) 表示環長為 \(k\) 的基環樹,限制環上\(\bmod d\) 相同的位置的子樹要完全一樣的方案數(不考慮旋轉同構)。那么 \(f_k(\frac k d)\) 就是要求\(\bmod \frac k d\) 相同的位置要完全一樣,即每個等價類的元素個數為 \(d\) 個。

那么我們就需要將所有點平均分成 \(d\) 份,然后求用其中的一份拼成 \(\frac k d\) 棵大小之和為 \(\frac n d\) 的無標號有根有序樹的方案數。無標號有根有序樹的 OGF 就是卡特蘭數的 OGF 乘上 \(x\),即 \(xC\),因為每一棵大小都不能為 \(0\),因此常數項為 \(0\)。那么 \(k\) 棵大小之和為 \(n\) 的樹的方案數就是 \([x^n](xC)^k\)

那么答案就可以寫成

\[\begin{aligned} \mathrm{Ans}&=\sum_{k=2}^n\frac{1}{k}\sum_{d|k}\left[d\:\middle|\:\gcd_{i=1}^na_i\right]\varphi(d)\cdot g(d)\cdot [x^\frac{n}{d}]{(xC)}^{\frac k d}\\ &=-\mathrm{Cat}_{n-1}g(1)+\sum_{d|\gcd}\varphi(d)g(d)\sum_{d|k}[x^\frac{n}{d}]\frac{{(xC)}^{\frac k d}}{k}\\ &=-\mathrm{Cat}_{n-1}g(1)+\sum_{d|\gcd}\frac{\varphi(d)g(d)}{d}\sum_{k=1}^{\frac n d}[x^{\frac{n}{d}-k}]\frac{C^{k}}{k} \end{aligned} \]

其中 \(g(d)=\frac{(n/d)!}{\prod(a_i/d)!}\),表示可重集排列的方案數,即安排好樹的形態后染色的方案數,計算一次需要 \(\mathcal O(m)\)

接着有兩條路可走,一條路是暴力生成函數

\[\begin{aligned} \mathrm{Ans}&=-\mathrm{Cat}_{n-1}g(1)+\sum_{d|\gcd}\frac{\varphi(d)g(d)}{d}[x^{\frac{n}{d}-k}]\sum_{k=1}^{\frac n d}\frac{C^{k}}{k}\\ &=-\mathrm{Cat}_{n-1}g(1)+\sum_{d|\gcd}\frac{\varphi(d)g(d)}{d}[x^{\frac{n}{d}-k}]\sum_{k=1}^{+\infty}\frac{C^{k}}{k}\\ &=-\mathrm{Cat}_{n-1}g(1)+\sum_{d|\gcd}\frac{\varphi(d)g(d)}{d}[x^{\frac{n}{d}-k}](-\ln(1-C)) \end{aligned} \]

時間復雜度 \(\mathcal O(n\log n+\sigma_0(n)m)\)


另一條路是智慧組合意義(我想不到):有一個很牛逼的性質是

\[\boldsymbol{[x^n]C^m=\binom{2n+m-1}{n}-\binom{2n+m-1}{n-1}} \]

證明:考慮 \([x^n]C^m\) 的組合意義,即所有合法括號序列划分成 \(m\) 段合法括號序列的方案數之和

卡特蘭數的其中一種含義是看成 \(n\)\(+1\)\(n\)\(-1\) 的任意排列,使得任意前綴和都 \(\geq 0\)

為了方便計算划分方案數,我們可以將合法划分看成在某些原本前綴和 \(=0\) 的位置后面加上一個 \(-1\),看成在這里划一段。故一種合法划分方案就能映射到 \(n\)\(+1\)\(n+m-1\)\(-1\) 的任意排列,使得任意前綴和都 \(\geq -m+1\)

那么我們考慮能否構造映射回來的方法,不難發現,第一段的末尾就是第一個前綴和為 \(-1\) 出現的位置、第二段的末尾就是第一個前綴和為 \(-2\) 出現的位置……這樣我們也構造了使得任意前綴和都 \(\geq -m+1\)\(n\)\(+1\)\(n+m-1\)\(-1\) 的任意排列,到合法划分方案的映射

這樣我們就建立了兩者的雙射。因此方案數相同。使得任意前綴和都 \(\geq -m+1\)\(n\)\(+1\)\(n+m-1\)\(-1\) 的任意排列數即為 \(\binom{2n+m-1}{n}-\binom{2n+m-1}{n-1}\)

那么直接根據這個式子計算即可

\[\mathrm{Ans}=-\mathrm{Cat}_{n-1}g(1)+\sum_{d|\gcd}\frac{\varphi(d)g(d)}{d}\sum_{k=1}^{\frac n d}[x^{\frac{n}{d}-k}]\frac{C^{k}}{k} \]

時間復雜度 \(\mathcal O(\sigma(n)+\sigma_0(n)m)\)

#include <bits/stdc++.h>

template <class T>
inline void read(T &x)
{
	static char ch; 
	while (!isdigit(ch = getchar())); 
	x = ch - '0'; 
	while (isdigit(ch = getchar()))
		x = x * 10 + ch - '0'; 
}

const int mod = 998244353; 

inline void add(int &x, const int &y)
{
	if (x += y, x >= mod)
		x -= mod; 
}

inline void dec(int &x, const int &y)
{
	if (x -= y, x < 0)
		x += mod; 
}

inline int minus(int x, int y)
{
	return x -= y, x < 0 ? x + mod : x; 
}

const int MaxN = 2e5 + 5; 
const int MaxM = MaxN * 3; 

int phi[MaxN]; 
int fac[MaxM], fac_inv[MaxM], ind[MaxM]; 

inline void phi_init(int n)
{
	for (int i = 1; i <= n; ++i)
	{
		phi[i] += i; 
		for (int j = i << 1; j <= n; j += i)
			dec(phi[j], phi[i]); 
	}
}

inline void fac_init(int n)
{
	fac[0] = fac_inv[0] = 1; 
	for (int i = 1; i <= n; ++i)
	{
		fac[i] = 1LL * fac[i - 1] * i % mod; 
		ind[i] = i == 1 ? 1 : 1LL * ind[mod % i] * (mod - mod / i) % mod; 
		fac_inv[i] = 1LL * fac_inv[i - 1] * ind[i] % mod; 
	}
}

inline int C(int n, int m)
{
	if (n < 0 || m < 0 || n < m) return 0; 
	return 1LL * fac[n] * fac_inv[m] % mod * fac_inv[n - m] % mod; 
}

inline int catalan_powm(int n, int m)
{
	return minus(C(n * 2 + m - 1, n), C(n * 2 + m - 1, n - 1)); 
}

int n, m, cnt[MaxN]; 

inline int prod_C(int d)
{
	int res = fac[n / d]; 
	for (int i = 1; i <= m; ++i)
		res = 1LL * res * fac_inv[cnt[i] / d] % mod; 
	return res; 
}

int main()
{
	read(n), read(m); 

	int D = n; 
	for (int i = 1; i <= m; ++i)
		read(cnt[i]), D = std::__gcd(D, cnt[i]); 

	phi_init(n); 
	fac_init(n * 3); 

	int ans = 0; 
	for (int d = 1; d <= D; ++d)
		if (D % d == 0)
		{
			int prod = 1LL * phi[d] * ind[d] % mod * prod_C(d) % mod; 
			int sum = 0; 

			for (int k = 1, lim = n / d; k <= lim; ++k)
				sum = (sum + 1LL * ind[k] * catalan_powm(lim - k, k)) % mod; 
			ans = (ans + 1LL * prod * sum) % mod; 
		}
	dec(ans, 1LL * catalan_powm(n - 1, 1) * prod_C(1) % mod); 

	std::cout << ans << std::endl; 

	return 0; 
}

6.2 無標號圖的計數

6.2.1 「SHOI2006」有色圖

Luogu #4128

弱化版:「HNOI2009」圖的同構記數(只需要將邊的有無,看成邊的染色就和這題完全一樣了)

\(n\) 個點的完全圖,點沒有顏色,邊有 \(m\) 種顏色,問本質不同的圖的數量對質數 \(p>n\) 取模。

本質不同指的是在點的 \(n!\) 種不同置換下不同。

\(n \leq 53\)

時空限制:\(1\texttt s/128\texttt{MB}\)

注意到本題的染色是邊到顏色的映射,而給出的置換是對點的置換,我們需要求出對應的邊置換才可以用 Burnside 引理。

首先我們考慮有標號圖上的點置換對邊的作用。對於一個點置換 \(\left(\begin{array}{c}{1,2,\cdots,n} \\{p_1,p_2,\cdots,p_n}\end{array}\right)\),我們就令對應的邊置換為 \(\left(\begin{array}{c}{(1,2),(1,3),\cdots,(i,j),\cdots,(n-1,n)} \\{(p_1,p_2),(p_1,p_3),\cdots,(p_i,p_j),\cdots(p_{n-1},p_n)}\end{array}\right)\),即我們建立了點置換到邊置換的映射

再考慮令這里的每個邊置換對應到唯一的點置換,將點置換和邊置換都分解成不相交的輪換,對於一個點置換中的輪換 \((a_1,a_2,\cdots,a_{k-1},a_k)\),在邊置換中一定存在這么一個輪換 \(\big((a_1,a_2),(a_2,a_3),\cdots,(a_{k-1},a_k),(a_k,a_1)\big)\)。因此對於邊置換中滿足首尾相連條件的輪換,我們就可以對應到一個點輪換。

因為這里的邊置換都是點置換映射到的,因此這里邊置換的乘法我們就可以看成對應的點置換的乘法,因此就滿足封閉性、結合律、單位元、逆元的性質。至此,我們證明了點置換和邊置換可以建立雙射,且這些邊置換構成一個群,大小也是 \(n!\)

我們將點置換分解成不相交的輪換,然后考慮對應的邊置換能分解成多少輪換,那么就可以求出在當前置換下的不動染色方案數。

先考慮邊的兩個端點屬於同一個輪換的情況。如下圖,相同顏色的邊(所有距離相同的點對之間的邊)表示屬於同一個邊輪換,需要染同一種顏色。(有的邊帶箭頭是為了看得清楚一點)。發現奇數和偶數的情況不太一樣,綜合一下兩個端點都屬於同一個大小為 \(k\) 的點輪換的邊,構成了 \(\lfloor\frac k 2\rfloor\) 個不相交的邊輪換。

然后考慮邊的兩個端點屬於不同輪換的情況。考慮兩個大小分別為 \(a_1,a_2\) 的點輪換,連接兩個點置換的邊一共有 \(a_1a_2\) 條。考慮對於其中一條邊,這兩個點置換同時作用在邊的兩個端點,我們發現恰好作用 \(\mathrm{lcm}(a_1,a_2)\) 次后兩個端點都回到原位,即這 \(a_1a_2\) 條邊分解成不相交的邊輪換后,每個邊輪換的大小為 \(\mathrm{lcm}(a_1,a_2)\),即邊輪換的個數為 \(\frac{a_1a_2}{\mathrm{lcm}(a_1,a_2)}=\gcd(a_1,a_2)\)

那么總的染色數就是 \(m\) 的邊輪換個數次方。即我們假設每個點輪換的大小為 \(a_1,a_2,\cdots,a_k\),其中 \(\sum a_i=n\)。那么這個點置換對應的邊置換下,不動的邊染色方案數就是

\[m^{\sum\limits_{i} \lfloor \frac{a_i}{2}\rfloor +\sum\limits_{i<j}\gcd(a_i,a_j)} \]

我們開始的想法是枚舉點置換,並以此計算對應邊置換的輪換數,從而算出在這種邊置換下的不動染色方案數。但是點置換的數量太多了,我們沒有辦法枚舉所有的點置換。

但是實際上,如果兩個置換分解出的不相交的輪換,大小划分相同,那么這兩種置換本質上是沒有區別的。那么我們可以就枚舉 \(n\) 的所有划分方案(寫成若干個正整數相加的形式),然后計算這種划分對應多少種置換,並計算這種划分的答案,那么我們就有辦法計算了。

對於一種划分方案 \(a_1\leq a_2\leq \cdots \leq a_k\)\(\sum a_i =n\),其對應的置換數一共有

\[\frac{n!}{\prod a_i!}\cdot \prod (a_i-1)! \cdot \frac{1}{k!}=\frac{n!}{\prod a_i\prod c_i!} \]

其中 \(c_i\) 表示這個划分中大小為 \(i\) 的輪換個數。

可以理解為,首先先划分每個點屬於哪個輪換,方案數是可重集排列數 \(\frac{n!}{\prod a_i!}\)。接着每個輪換內部是一個圓排列,方案數即為 \(\prod (a_i-1)!\)。最后還要考慮輪換之間是沒有區別的,需要除以相同大小的輪換數階乘乘積 \(\prod c_i!\)

那么因為 \(n!\) 與總的置換數抵消了,答案就是

\[\sum_{\sum a_i=n\\~a_i\leq a_{i+1}}\frac{m^{\sum\limits_{i} \lfloor \frac{a_i}{2}\rfloor +\sum\limits_{i<j}\gcd(a_i,a_j)}}{\prod a_i\prod c_i!} \]

時間復雜度為 \(\mathcal O\left(\sum\limits_{p\in \text{Partition}(n)}\mathrm{len}(p)^2\log n\right)\),實際比較小。

#include <bits/stdc++.h>

const int MaxN = 66; 

int n, m, mod;
int fac[MaxN], fac_inv[MaxN], ind[MaxN]; 

int ans; 
int cnt, a[MaxN]; 

inline void add(int &x, const int &y)
{
	x += y; 
	if (x >= mod)
		x -= mod; 
}

inline int qpow(int x, int y)
{
	int res = 1; 
	for (; y; y >>= 1, x = 1LL * x * x % mod)
		if (y & 1)
			res = 1LL * res * x % mod; 
	return res; 
}

inline void fac_init(int n)
{
	fac[0] = fac[1] = fac_inv[0] = fac_inv[1] = ind[1] = 1; 
	for (int i = 2; i <= n; ++i)
	{
		fac[i] = 1LL * fac[i - 1] * i % mod; 
		ind[i] = 1LL * ind[mod % i] * (mod - mod / i) % mod; 
		fac_inv[i] = 1LL * fac_inv[i - 1] * ind[i] % mod; 
	}
}

inline void dfs(int k, int rest, int lst = 1)
{
	if (!rest)
	{
		cnt = k - 1; 

		int res = 0; 
		for (int i = 1; i <= cnt; ++i)
		{
			res += a[i] >> 1; 
			for (int j = i - 1; j >= 1; --j)
				res += std::__gcd(a[i], a[j]); 
		}

		int cur = qpow(m, res), lst = 0, c = 0; 
		for (int i = 1; i <= cnt; ++i)
		{
			if (lst == a[i])
				++c; 
			else
			{
				cur = 1LL * cur * fac_inv[c] % mod; 
				lst = a[i], c = 1; 
			}

			cur = 1LL * cur * ind[a[i]] % mod; 
		}
		if (c > 1)
			cur = 1LL * cur * fac_inv[c] % mod; 

		add(ans, cur); 
	}
	for (int i = lst; i <= rest; ++i)
	{
		a[k] = i; 
		dfs(k + 1, rest - i, i); 
	}
}

int main()
{
	std::cin >> n >> m >> mod; 
	fac_init(n); 

	dfs(1, n); 
	std::cout << ans << '\n'; 

	return 0; 
}

6.2.2 畫畫

Luogu #4708

求有多少個本質不同的無重邊無自環的無向圖,使得每個連通塊都有歐拉回路。

兩個圖本質相同,當且僅當存在一個點到點的置換,使得對於原圖和在置換作用下的新圖,任意兩點之間要么都沒有連邊,要么都有連邊。

\(1 \leq n \leq 50\)

首先每個連通塊都有歐拉回路,當且僅當每個點的度數均為偶數。邊的有無仍然可以看成染色。

那么和上面那題類似,我們可以在點置換和邊置換之間建立雙射,並且用 Burnside 引理轉化成在有標號圖上算每個置換下的不動染色方案數。

現在的問題就是怎么計算合法的不動染色方案數,我們先考慮將邊置換分解成不相交的邊輪換,那么每個邊輪換內部的邊必須同時選擇或者同時不選擇,然后看看每個邊輪換中的邊選擇或者不選擇會有什么影響。

對於邊的兩個端點都在同一個大小為 \(k\) 的點輪換的邊,根據前一題的推理,會有如下圖的 \(\lfloor \frac k 2\rfloor\) 個邊輪換。但是我們發現,這 \(\lfloor \frac k 2\rfloor\) 個邊輪換產生的效果不一定相同。具體地:
\(k\) 是偶數時,有 \(\frac{k-1}{2}\) 個邊輪換,它們選或不選都不會改變點度數的奇偶性,故可以任意染色,方案數是 \(2^{\frac{k-1}{2}}\)
\(k\) 是奇數時,有 \(\frac{k}{2}-1\) 個邊輪換選或不選都不會改變點度數的奇偶性,方案數是 \(2^{\frac{k}{2}-1}\)。但是 恰有一個邊輪換,如果選了這個邊輪換的邊,那么\(k\) 個點的度數奇偶性均取反

(是的,你沒看錯,和上面那題的圖一模一樣)

接着我們考慮邊的兩個端點不在同一個點輪換的邊。對於兩個大小分別為 \(a_1,a_2\) 的點輪換,根據上一題的推理,我們得到了 \(\frac{a_1a_2}{\mathrm{lcm}(a_1,a_2)}=\gcd(a_1,a_2)\) 個邊輪換,每個邊輪換有 \(\mathrm{lcm}(a_1,a_2)\) 條邊。

那么在一個邊輪換中,和 \(a_1\) 的每個點相連的邊有 \(e_1=\frac{\mathrm{lcm}(a_1,a_2)}{a_1}=\frac{a_2}{\gcd(a_1,a_2)}\) 條,和 \(a_2\) 的每個點相連的邊有 \(e_2=\frac{a_1}{\gcd(a_1,a_2)}\) 條。

根據 \(e_1,e_2\) 的奇偶性,我們就能知道每個邊輪換選或不選產生的影響。

  • \(e_1,e_2\) 均為偶數,那么每個邊輪換選或不選都不改變點的奇偶性,方案數是 \(2^{\gcd(a_1,a_2)}\)
  • \(e_1,e_2\) 均為奇數,那么每個邊輪換選擇即會使兩個點輪換中的每個點的度數奇偶性均取反
  • \(e_1,e_2\) 中恰有一個奇數,每個邊輪換選擇即會使其中一個點輪換中的每個點的度數奇偶性均取反

那么因為取反操作都是基於整個點輪換的,那么我們可以將每個點輪換縮成一個點。對於任意選取而不影響奇偶性的邊輪換,我們直接乘上 \(2\) 的次冪的系數。
簡化之后的問題就是,給定一張無向圖,每個點有自然數點權,每條邊有自然數邊權——每個點 \(x\) 的點權 \(v_x\) 表示有 \(v_x\) 次機會將這個點的度數奇偶性取反,每條邊 \(e\) 的邊權 \(w_e\) 表示有 \(w_e\) 次機會將這條邊的兩個端點的度數奇偶性一起取反。

對於邊權不為 \(0\) 的邊形成的點連通塊,我們可以分開考慮。對於某個點數為 \(s\) 的連通塊,考慮這個連通塊的某棵生成樹,如果我們確認了點的選擇情況和非樹邊的選擇情況(選擇情況指的是操作次數的奇偶性),那么可以證明,如果恰好選擇了偶數個點改變奇偶性,樹邊的選擇情況是唯一確定的;否則不存在合法情況。
首先考慮,如果改變了奇數個點的奇偶性,因為改變一次邊總是改變兩個點的奇偶性,所有點的奇偶性之和仍是奇數,顯然不存在合法情況。接着如果我們確定了點和非樹邊的選擇情況,那么樹邊的選擇情況就能唯一確定(可以考慮從葉子推上來)。

因此對於一個大小為 \(s\) 的連通塊,方案數就是

\[2^{\max(0,\sum v_x-1)+\sum w_e-s+1} \]

那么時間復雜度就是 \(\mathcal O\left(\sum\limits_{p\in \text{Partition}(n)}\mathrm{len}(p)^2\log n\right)\),可以通過本題。

#include <bits/stdc++.h>

const int MaxN = 66; 
const int mod = 998244353; 

inline void add(int &x, const int &y)
{
	x += y; 
	if (x >= mod)
		x -= mod; 
}

inline int qpow(int x, int y)
{
	int res = 1; 
	for (; y; y >>= 1, x = 1LL * x * x % mod)
		if (y & 1)
			res = 1LL * res * x % mod; 
	return res; 
}

int n;
int fac[MaxN], fac_inv[MaxN], ind[MaxN]; 

int ans; 
int cnt, a[MaxN]; 

inline void fac_init(int n)
{
	fac[0] = fac[1] = fac_inv[0] = fac_inv[1] = ind[1] = 1; 
	for (int i = 2; i <= n; ++i)
	{
		fac[i] = 1LL * fac[i - 1] * i % mod; 
		ind[i] = 1LL * ind[mod % i] * (mod - mod / i) % mod; 
		fac_inv[i] = 1LL * fac_inv[i - 1] * ind[i] % mod; 
	}
}

inline int calc_same()
{
	int cur = 1; 
	int lst = 0, c = 0; 
	for (int i = 1; i <= cnt; ++i)
	{
		if (lst == a[i])
			++c; 
		else
		{
			cur = 1LL * cur * fac_inv[c] % mod; 
			lst = a[i], c = 1; 
		}

		cur = 1LL * cur * ind[a[i]] % mod; 
	}
	if (c > 1)
		cur = 1LL * cur * fac_inv[c] % mod; 
	return cur;  
}

int deg[MaxN], ufs[MaxN]; 
int sze[MaxN], sump[MaxN], sume[MaxN]; 

inline void ufs_init(int n)
{
	for (int i = 1; i <= n; ++i)
	{
		ufs[i] = i; 
		sze[i] = 1; 
		sump[i] = sume[i] = 0; 
	}
}

inline int ufs_find(int x)
{
	if (x == ufs[x])
		return x; 
	return ufs[x] = ufs_find(ufs[x]); 
}

inline void ufs_merge(int x, int y)
{
	x = ufs_find(x);
	y = ufs_find(y); 

	if (x == y) return; 

	ufs[y] = x; 
	sze[x] += sze[y]; 
	sump[x] += sump[y]; 
	sume[x] += sume[y]; 
}

inline int solve()
{
	ufs_init(cnt); 

	int res = 0; 
	for (int i = 1; i <= cnt; ++i)
	{
		if (~a[i] & 1)
			++sump[ufs_find(i)]; 
		res += (a[i] - 1) >> 1; 

		for (int j = 1; j < i; ++j)
		{
			int d = std::__gcd(a[i], a[j]); 
			int n1 = a[j] / d, n2 = a[i] / d; 
			if ((n1 & 1) && (n2 & 1))
			{
				ufs_merge(i, j); 
				sume[ufs_find(i)] += d; 
			}
			else if (n1 & 1)
				sump[ufs_find(i)] += d; 
			else if (n2 & 1)
				sump[ufs_find(j)] += d; 
			else
				res += d; 
		}
	}

	for (int i = 1; i <= cnt; ++i)
		if (ufs_find(i) == i)
			res += std::max(sump[i] - 1, 0) + sume[i] - (sze[i] - 1); 
	return res; 
}

inline void dfs(int k, int rest, int lst = 1)
{
	if (!rest)
	{
		cnt = k - 1; 
		add(ans, 1LL * qpow(2, solve()) * calc_same() % mod); 
	}
	for (int i = lst; i <= rest; ++i)
	{
		a[k] = i; 
		dfs(k + 1, rest - i, i); 
	}
}

int main()
{
	std::cin >> n; 
	fac_init(n); 

	dfs(1, n); 
	std::cout << ans << '\n'; 

	return 0; 
}

6.3 其他問題

先咕咕咕

6.3.1 烷基計數和烷烴計數

6.3.2 無標號有根樹計數和無標號無根樹計數

注:該問題的重點應當是生成函數和多項式,Burnside 引理只在其中一種解決方法中起到較小的作用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM