五边形数学习小记

学五边形数就是为了整数划分一类问题,目前并不知道有什么其它用途。

设整数划分的生成函数为 P ( x )

P ( x ) = i = 1 ( j = 1 x i j )
= i = 1 1 1 x i

有一不是数论上那个phi的函数 ϕ ( x ) ,函数式为:
ϕ ( x ) = i = 1 ( 1 x i )

显然有 ϕ ( x ) p ( x ) = 1

那么我们只需要求出 ϕ ( x ) ,通过多项式求逆的算法即可求出 P ( x )

我们用一个五边形数定理就能搞定 ϕ ( x )
ϕ ( x ) = 1 + i = 1 ( 1 ) i x i ( 3 i ± 1 ) / 2

证明推荐一篇博客:
《五边形数定理的一种证明》

注意到i在指数中是二次的,也就是说 [ x 1 n ] ϕ ( x ) 只有大约 n 个不为0。

这样的话暴力求逆回去就是 O ( n n ) 的,与普通dp的复杂度一样,优势在于可以多组询问。

当然可以用NTT多项式求逆,复杂度 O ( n   l o g 2 n )

裸题是hdu 4651

没那么裸的是hdu 4658

第二题的生成函数是:
P ( x ) = i = 1 ( j = 1 k = 1 x i j )
= i = 1 1 x k i 1 x i
= ϕ ( x k ) ϕ ( x )

然后一次询问是 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);
    }
}

猜你喜欢

转载自blog.csdn.net/Cold_Chair/article/details/81534683