学五边形数就是为了整数划分一类问题,目前并不知道有什么其它用途。
设整数划分的生成函数为 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
#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
#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);
}
}