学五边形数就是为了整数划分一类问题,目前并不知道有什么其它用途。
设整数划分的生成函数为 P(x) P ( x )
P(x)=∏∞i=1(∑∞j=1xij)
P
(
x
)
=
∏
i
=
1
∞
(
∑
j
=
1
∞
x
i
j
)
=∏∞i=111−xi
=
∏
i
=
1
∞
1
1
−
x
i
有一不是数论上那个phi的函数
ϕ(x)
ϕ
(
x
)
,函数式为:
ϕ(x)=∑∞i=1(1−xi)
ϕ
(
x
)
=
∑
i
=
1
∞
(
1
−
x
i
)
显然有 ϕ(x)p(x)=1 ϕ ( x ) p ( x ) = 1
那么我们只需要求出 ϕ(x) ϕ ( x ) ,通过多项式求逆的算法即可求出 P(x) P ( x )
我们用一个五边形数定理就能搞定
ϕ(x)
ϕ
(
x
)
:
ϕ(x)=1+∑∞i=1(−1)ixi∗(3i±1)/2
ϕ
(
x
)
=
1
+
∑
i
=
1
∞
(
−
1
)
i
x
i
∗
(
3
i
±
1
)
/
2
证明推荐一篇博客:
《五边形数定理的一种证明》
注意到i在指数中是二次的,也就是说 [x1−n]ϕ(x) [ x 1 − n ] ϕ ( x ) 只有大约 n−−√ n 个不为0。
这样的话暴力求逆回去就是 O(nn−−√) O ( n n ) 的,与普通dp的复杂度一样,优势在于可以多组询问。
当然可以用NTT多项式求逆,复杂度 O(n log2n) O ( n l o g 2 n ) 。
裸题是hdu 4651。
没那么裸的是hdu 4658。
第二题的生成函数是:
P(x)=∏∞i=1(∑k=1j=1xij)
P
(
x
)
=
∏
i
=
1
∞
(
∑
j
=
1
k
=
1
x
i
j
)
=∏∞i=11−xki1−xi
=
∏
i
=
1
∞
1
−
x
k
i
1
−
x
i
=ϕ(xk)ϕ(x)
=
ϕ
(
x
k
)
ϕ
(
x
)
然后一次询问是 O(n−−√) O ( n ) 。
Code(T1):
#include<cstdio>
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fu(a) ((a) & 1 ? -1 : 1)
using namespace std;
const int mo = 1e9 + 7;
int T, n; ll phi[100005];
int main() {
n = 100000; phi[0] = 1;
fo(i, 1, n) {
fo(j, 1, i) {
int k = j * (3 * j - 1) / 2;
if(k <= i) phi[i] = (phi[i] - fu(j) * phi[i - k] + mo) % mo; else break;
k = j * (3 * j + 1) / 2;
if(k <= i) phi[i] = (phi[i] - fu(j) * phi[i - k] + mo) % mo; else break;
}
}
for(scanf("%d", &T); T; T --)
scanf("%d", &n), printf("%lld\n", phi[n]);
}
Code(T2):
#include<cstdio>
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fu(a) ((a) & 1 ? -1 : 1)
using namespace std;
const int mo = 1e9 + 7;
int T, n, k; ll p[100005], s;
int main() {
n = 100000; p[0] = 1;
fo(i, 1, n) {
fo(j, 1, i) {
int k = j * (3 * j - 1) / 2;
if(k <= i) p[i] = (p[i] - fu(j) * p[i - k] + mo) % mo; else break;
k = j * (3 * j + 1) / 2;
if(k <= i) p[i] = (p[i] - fu(j) * p[i - k] + mo) % mo; else break;
}
}
for(scanf("%d", &T); T; T --) {
scanf("%d %d", &n, &k);
s = p[n];
fo(i, 1, n) {
int j = i * (3 * i - 1) / 2;
if(j * k <= n) s = (s + fu(i) * p[n - j * k] + mo) % mo; else break;
j = i * (3 * i + 1) / 2;
if(j * k <= n) s = (s + fu(i) * p[n - j * k] + mo) % mo; else break;
}
printf("%lld\n", s);
}
}