【生成函數】五邊形數定理與整數劃分問題詳解

Part 0. 前置知識

  • 簡單的公式推導
  • 生成函數

大量數學公式警告

Part 1. 什麼是五邊形數

五邊形數

看一張圖吧:

XP 系統帶的畫圖真的挺好用!

我們不難得出五邊形數的數列:

P={1,5,12,22,} P = \{1, 5, 12, 22,\ldots\}

再找一下規律,我們就可以得到五邊形數的通項公式:

Pn=n(3n1)2 P_n = \frac{n(3n - 1)}{2}

似乎現在並沒有什麼用。。。

廣義五邊形數

也就是當 nn 可以爲負數的時候的五邊形數序列,它的通項公式爲:

Pn=n(3n±1)2 P_n = \frac{n(3n \pm 1)}{2}

Part 2. 五邊形數定理

定義歐拉函數(注意不要把它看成數論裏面的歐拉函數)爲:

ϕ(n)=i=1(1xi) \phi (n) = \prod\limits_{i = 1}^{\infty} (1 - x^i)

則五邊形數定理就是:

ϕ(n)=i=(1)ixi(3i1)2=1+i=0(1)ixi(3i±1)2 \phi (n) = \prod\limits_{i = -\infty}^{\infty}(-1)^ix^{\frac{i(3i - 1)}{2}} = 1 + \prod\limits_{i = 0}^{\infty}(-1)^ix^{\frac{i(3i \pm 1)}{2}}

證明等一會給出。

Part 3. Ferrers 圖

Ferrers 圖是一個由 kk 行的點陣構成的方格圖,要求上一行的格子數目不超過它下面一行的格子的數目,其中總和爲 nn 的數量。

舉個例子:(n=20n = 20 時 的一個圖,我用 @ 來表示一個格子)

@@@@@@
@@@@@
@@@@
@@@
@@

我們記這時的第 kk 行的格子數量爲 mmss 爲第一行最右邊的 @ 右上角的對角線格子數量。

定義一種變換:

  • m>sm > s 時, 把最右邊的對角線上的格子扔到新的一行。
  • msm \le s 時,把最後一行的所有格子分給從第一行開始的每行上,每行分到一個格子。

舉個例子:(用 $ 表示被移走的格子)

@@@@@@@@        @@@@@@@@$
@@@@@@@         @@@@@@@$
@@@@@    <=>    @@@@@
@@@             @@@
$$            

不難看出這個操作是顯然可逆的。

這個操作還有一些性質:

我們對一個 Ferrers 圖進行連續的兩次變換後,它會變回它本身。

如果我們僅進行一次變換,那麼行數的奇偶性可以改變。

Part 4. 五邊形數定理的證明

我們將 ϕ(n)\phi(n) 的定義式展開之後可以發現,第 nn 項的係數應該是將 nn 劃分成偶數個互不相同的正整數的答案減掉將 nn 劃分成奇數個互不相同的正整數方案,每一項的係數應該都是 00。但實際操作後發現有些項前面是有係數的。

那麼我們考慮仔細研究一下 Ferrers 圖。


m=s+1m = s + 1 時,且底層的最右邊的元素與對角線相遇,如下:

@@@@@@    @@@@@
@@@@@  => @@@@
@@@@      @@@
          @@@

發現此時無法操作回去了。這是一個不合法的狀態。

t=1mt = 1 - m,可以得到 n=(1t)+(2t)+(3t)++(2t)=t(3t1)2n = (1 - t) + (2 - t) + (3 - t) + \cdots + (-2t) = \frac{t(3t - 1)}{2},這一項對答案的貢獻爲 (1)t(-1) ^ t


m=sm = s,且底層的最右邊的元素與對角線相遇,如下:

@@@@@    @@@@@@
@@@@  => @@@@@
@@@      @

這個顯然是不合法的,令 t=mt = m,可以得到 n=t+(t+1)+(t+2)++2t=t(3t+1)2n = t + (t + 1) + (t + 2) + \cdots + 2t = \frac{t(3t + 1)}{2},對第 nn 項的貢獻爲 (1)t(-1) ^ t


這樣一來,當 n=k(3k±1)2n = \frac{k(3k \pm 1)}{2} 時,即 nn 是一個廣義五邊形數時,nn 的奇偶拆分就在抵消之後留下一項。所以五邊形定理成立。

Part 5. 整數劃分問題

問題概要

將一個整數 nn 劃分成任意個可以相等的整數,求方案數。

分析

我們不難寫出一個 O(n2)O(n^2) 的 DP 來解決整數劃分問題。但當 nn 超過 10410^4 之後就跑不動了。

考慮高效解法 (利用五邊形數定理)

定義函數 P(i)P(i) 爲將 nn 拆分成若干個可以相等的整數的方案數。

考慮 P(i)P(i) 的生成函數 F(x)F(x)

F(x)=i=1P(i)xi=i=1(x0+xi+x2i+)=i=111xi \begin{aligned} F(x) & = \sum\limits_{i = 1}^{\infty}P(i)x^i \\ &= \prod\limits_{i = 1}^{\infty}(x^0 + x^i + x^{2i} + \cdots) \\ & =\prod\limits_{i = 1}^{\infty} \frac{1}{1 - x^i} \end{aligned}

再看一下上面提到的歐拉函數:

ϕ(x)=i=1(1xi) \phi(x) = \prod\limits_{i = 1}^{\infty} (1 - x^i)

顯然可以得到:F(x)ϕ(x)=1F(x)\cdot\phi(x) = 1

然後根據五邊形數定理將 ϕ(x)\phi(x) 代入:

[i=1P(i)xi](i=01+i=0(1)ixi(3i±1)2)=1 \left[\sum\limits_{i = 1}^{\infty}P(i)x^i\right] \cdot \left(\sum\limits_{i = 0}^{\infty}1 + \prod\limits_{i = 0}^{\infty}(-1)^ix^{\frac{i(3i \pm 1)}{2}}\right) = 1

直接暴力展開可以得到:

P(n)P(n1)P(n2)+P(n5)+P(n7)=0 P(n) - P(n - 1) - P(n - 2) + P(n - 5) + P(n - 7) - \cdots = 0

不難發現減去的每一項都是廣義五邊形數。於是直接暴力計算即可。

由於五邊形數 pn=n(3n±1)2p_n = \frac{n(3n \pm 1)}{2} 的增長速度爲 O(n2)O(n ^ 2)。所以最多枚舉不超過 O(n)O(\sqrt n) 項五邊形數就可以退出循環,故時間複雜度爲 O(nn)O(n\sqrt n)

Part 6. 例題

HDU 4651

題目大意

求將正整數 nn 劃分成多個正整數的和的方案數。

分析

就是一個模板了。。。好好看一看上面的就可以了。

HDU 4658

題目大意

將一個正整數 nn 劃分爲多個正整數的和的方案數,但每個數的使用次數不能夠大於等於 kk 次。

分析

記這種方式下的劃分數爲 P(n)P'(n)。先給出生成函數 G(x)=i=1(1+xi+x2i++x(k1)i)G(x) = \prod\limits_{i = 1}^{\infty}(1 + x^i + x^{2i} + \cdots + x^{(k - 1)i})

嘗試化簡:

G(x)=i=11+xi+xi+xi+1+xki+x2ki+x3ki+=i=111xi11xki=i=11xki1xi=ϕ(xk)ϕ(x)=ϕ(xk)F(x) \begin{aligned} G(x) & = \prod\limits_{i = 1}^{\infty}\frac{1+ x^i + x^i + x^i + \cdots}{1 + x^{ki} + x^{2ki} + x^{3ki} + \cdots} \\ & = \prod_{i = 1}^{\infty}\frac{\frac{1}{1 - x^i}}{\frac{1}{1 - x^{ki}}} \\ & = \prod_{i = 1}^{\infty}\frac{1 - x^{ki}}{1 - x^i} \\ & = \frac{\phi(x^k)}{\phi(x)} \\ & = \phi(x^k) \cdot F(x) \end{aligned}

然後暴力展開 ϕ(xk)F(x)\phi(x^k) \cdot F(x) 得到:

i=1P(i)xi=(1xkx2k+x5k+)[1+P(1)x+P(2)x2+] \sum\limits_{i = 1}^{\infty}P'(i)x^i = (1 - x^k - x^{2k} + x^{5k} + \cdots)[1 + P(1)x + P(2)x^2 + \cdots]

然後可以得到 P(i)=P(i)P(ik)P(i2k)+P(i5k)+P'(i) = P(i) - P(i - k) - P(i - 2k) + P(i - 5k) + \cdots

於是就可以在 O(n)O(\sqrt n) 的時間內回答詢問了,總時間複雜度爲 O(nn+Tn)O(n\sqrt n + T\sqrt n)

參考代碼

HDU 4651

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ll;
const int Maxn = 1e5;
const ll Mod = 1000000007;

ll f[Maxn * 2 + 5];
ll p[Maxn + 5];
void Init() {
	for(int i = -Maxn; i <= Maxn; i++)
		f[i + Maxn] = 1LL * i * (i * 3 - 1) / 2;
	//計算五邊形數
	p[0] = 1;
	for(int i = 1; i <= Maxn; i++)
		for(int j = 1; j <= i; j++) {
			if(f[j + Maxn] <= i) {
				if(j & 1) p[i] = (p[i] + p[i - f[j + Maxn]]) % Mod;
				else p[i] = (p[i] - p[i - f[j + Maxn]] + Mod) % Mod;
			} else break;//根據 j 的奇偶性來判斷當前應該加還是應該減
			if(f[Maxn - j] <= i) {
				if(j & 1) p[i] = (p[i] + p[i - f[Maxn - j]]) % Mod;
				else p[i] = (p[i] - p[i - f[Maxn - j]] + Mod) % Mod;
			} else break;
		}
}

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	Init();
	int _;
	scanf("%d", &_);
	while(_--) {
		int n;
		scanf("%d", &n);
		printf("%lld\n", p[n]);
	}
	return 0;
}

HDU 4658

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ll;
const int Maxn = 1e5;
const ll Mod = 1e9 + 7;

ll f[Maxn * 2 + 5];
ll p[Maxn + 5];
ll ans[Maxn + 5];
void Init() {
	for(int i = -Maxn; i <= Maxn; i++)
		f[i + Maxn] = 1LL * i * (3 * i - 1) / 2;
	p[0] = 1;
	for(int i = 1; i <= Maxn; i++)
		for(int j = 1; j <= i; j++) {
			if(f[j + Maxn] <= i) {
				if(j & 1) p[i] = (p[i] + p[i - f[j + Maxn]]) % Mod;
				else p[i] = (p[i] - p[i - f[j + Maxn]] + Mod) % Mod;
			} else break;
			if(f[Maxn - j] <= i) {
				if(j & 1) p[i] = (p[i] + p[i - f[Maxn - j]]) % Mod;
				else p[i] = (p[i] - p[i - f[Maxn - j]] + Mod) % Mod;
			} else break;
		}
}

ll Solve(int n, int k) {
	ll ret = p[n], dir = -1;
	for(int i = 1; ; i++) {
		ll val1 = 1LL * k * i * (3 * i - 1) / 2,
			val2 = 1LL * k * i * (3 * i + 1) / 2;
		if(val1 > n && val2 > n) break;
		if(val1 <= n) ret = (ret + dir * p[n - val1] + Mod) % Mod;
		if(val2 <= n) ret = (ret + dir * p[n - val2] + Mod) % Mod;
		dir *= -1;
	}
	return ret;
}

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	Init();
	int _;
	scanf("%d", &_);
	while(_--) {
		int n, k;
		scanf("%d %d", &n, &k);
		printf("%lld\n", Solve(n, k));
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章