Part 0. 前置知識
- 簡單的公式推導
- 生成函數
大量數學公式警告
Part 1. 什麼是五邊形數
五邊形數
看一張圖吧:
XP 系統帶的畫圖真的挺好用!
我們不難得出五邊形數的數列:
再找一下規律,我們就可以得到五邊形數的通項公式:
似乎現在並沒有什麼用。。。
廣義五邊形數
也就是當 可以爲負數的時候的五邊形數序列,它的通項公式爲:
Part 2. 五邊形數定理
定義歐拉函數(注意不要把它看成數論裏面的歐拉函數)爲:
則五邊形數定理就是:
證明等一會給出。
Part 3. Ferrers 圖
Ferrers 圖是一個由 行的點陣構成的方格圖,要求上一行的格子數目不超過它下面一行的格子的數目,其中總和爲 的數量。
舉個例子:( 時 的一個圖,我用 @ 來表示一個格子)
@@@@@@
@@@@@
@@@@
@@@
@@
我們記這時的第 行的格子數量爲 , 爲第一行最右邊的 @ 右上角的對角線格子數量。
定義一種變換:
- 當 時, 把最右邊的對角線上的格子扔到新的一行。
- 當 時,把最後一行的所有格子分給從第一行開始的每行上,每行分到一個格子。
舉個例子:(用 $ 表示被移走的格子)
@@@@@@@@ @@@@@@@@$
@@@@@@@ @@@@@@@$
@@@@@ <=> @@@@@
@@@ @@@
$$
不難看出這個操作是顯然可逆的。
這個操作還有一些性質:
我們對一個 Ferrers 圖進行連續的兩次變換後,它會變回它本身。
如果我們僅進行一次變換,那麼行數的奇偶性可以改變。
Part 4. 五邊形數定理的證明
我們將 的定義式展開之後可以發現,第 項的係數應該是將 劃分成偶數個互不相同的正整數的答案減掉將 劃分成奇數個互不相同的正整數方案,每一項的係數應該都是 。但實際操作後發現有些項前面是有係數的。
那麼我們考慮仔細研究一下 Ferrers 圖。
當 時,且底層的最右邊的元素與對角線相遇,如下:
@@@@@@ @@@@@
@@@@@ => @@@@
@@@@ @@@
@@@
發現此時無法操作回去了。這是一個不合法的狀態。
令 ,可以得到 ,這一項對答案的貢獻爲 。
當 ,且底層的最右邊的元素與對角線相遇,如下:
@@@@@ @@@@@@
@@@@ => @@@@@
@@@ @
這個顯然是不合法的,令 ,可以得到 ,對第 項的貢獻爲 。
這樣一來,當 時,即 是一個廣義五邊形數時, 的奇偶拆分就在抵消之後留下一項。所以五邊形定理成立。
Part 5. 整數劃分問題
問題概要
將一個整數 劃分成任意個可以相等的整數,求方案數。
分析
我們不難寫出一個 的 DP 來解決整數劃分問題。但當 超過 之後就跑不動了。
考慮高效解法 (利用五邊形數定理)
定義函數 爲將 拆分成若干個可以相等的整數的方案數。
考慮 的生成函數 :
再看一下上面提到的歐拉函數:
顯然可以得到:。
然後根據五邊形數定理將 代入:
直接暴力展開可以得到:
不難發現減去的每一項都是廣義五邊形數。於是直接暴力計算即可。
由於五邊形數 的增長速度爲 。所以最多枚舉不超過 項五邊形數就可以退出循環,故時間複雜度爲 。
Part 6. 例題
HDU 4651
題目大意
求將正整數 劃分成多個正整數的和的方案數。
分析
就是一個模板了。。。好好看一看上面的就可以了。
HDU 4658
題目大意
將一個正整數 劃分爲多個正整數的和的方案數,但每個數的使用次數不能夠大於等於 次。
分析
記這種方式下的劃分數爲 。先給出生成函數 。
嘗試化簡:
然後暴力展開 得到:
然後可以得到
於是就可以在 的時間內回答詢問了,總時間複雜度爲 。
參考代碼
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;
}