【HDU】4658 Integer Partition【生成函数——数拆分】

传送门:【HDU】4658 Integer Partition

题目分析:

用了五边形数定理以及生成函数,然而我看懂了生成函数怎么搞这题却不知道为啥生成函数是五边形数形式= =

首先观察下面的图片:

五边形数

很容易我们可以发现用这种方式构造N个五边形(假设一个点也算一个五边形),需要点的个数为:

n(3n1)2


接下来我们来看一下数拆分。
提问:将一个正整数 N 拆成不少于一个数的和,问有多少种方案。

很容易我们可以构造一个多项式:
P(x)=(1+x1+x2+...)(1+x2+x4+...)(1+x3+x6+...)...
=Px(0)x0+Px(1)x1+Px(2)x2+...+Px(n)xn

可以发现N的数拆分的方案数正对应着多项式展开后 xn 的系数 Px(n)

考虑如下等式:

(1+x1+x2+...)=11x

因此我们有:
i=011xi=i=0Px(i)xi

其中上式等式左边是欧拉函数 ϕ(x) 的倒数。即:
1ϕ(x)=i=0Px(i)xi

欧拉函数 ϕ(x) 的展开式为:

ϕ(x)=(1x)(1x2)(1x3)...=1xx2+x5+x7x12x15+x22+x26...

其中的x的指数正对应着广义五边形数!

n 0 1 -1 2 -2 3 -3 4 -4
P(n) 0125712152226

现在我们要计算 Px(n) ,由于 1ϕ(x)=P(x) ,亦即 ϕ(x)P(x)=1

(1xx2+x5+...)(Px(0)+Px(1)x+Px(2)x2+Px(3)x3+...)=1

所以: Px(n)=Px(n1)+Px(n2)Px(n5)Px(n7)+...

由于对于满足 i(3i1)2n i 的个数不超过(n)个,于是计算所有 Px(n) 的复杂度为 O(n(n))


上面我们说明的是不带限制的数拆分,现在我们给定一个限制:拆分出来的每种数的个数不能大于等于k(这也是本题的要求)。

类似的,我们考虑生成函数:

(1+x1+x2+...+xk1)(1+x2+x4+...x2(k1))...=i=01xki1xi=ϕ(xk)ϕ(x)=ϕ(xk)P(x)

展开 ϕ(xk) 得:
(1xk)(1x2k)(1x3k)...=1xkx2k+x5k+x7kx12kx15k+...

然后可得:
ϕ(xk)P(x)=(1xkx2k+x5k...)(Px(0)+Px(1)x+Px(2)x2+Px(3)x3+...)

Fk(n) 表示n的满足数拆分时每种数的个数小于等于k的数拆分方案数。则有:
Fk(n)=Px(n)Px(nk)Px(n2k)+Px(n5k)+...

my  code:

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <bitset>
#include <map>
#include <set>
#include <algorithm>
using namespace std ;

typedef long long LL ;

const int MAXN = 100005 ;
const int mod = 1e9 + 7 ;

int p[MAXN] ;
int t1[MAXN] , t2[MAXN] ;
int n , k ;

void add ( int& x , int y ) {
    x += y ;
    while ( x >= mod ) x -= mod ;
    while ( x < 0 ) x += mod ;
}

void preprocess () {
    p[0] = 1 ;
    for ( int i = 1 ; ; ++ i ) {
        t1[i] = i * ( 3 * i - 1 ) / 2 ;
        t2[i] = i * ( 3 * i + 1 ) / 2 ;
        if ( t1[i] >= MAXN ) break ;
    }
    for ( int i = 1 ; i < MAXN ; ++ i ) {
        p[i] = 0 ;
        for ( int j = 1 , flag = 1 ; ; ++ j , flag *= -1 ) {
            if ( t1[j] <= i ) add ( p[i] , flag * p[i - t1[j]] ) ;
            else break ;
            if ( t2[j] <= i ) add ( p[i] , flag * p[i - t2[j]] ) ;
            else break ;
        }
    }
}

void solve () {
    scanf ( "%d%d" , &n , &k ) ;
    int ans = p[n] ;
    for ( int j = 1 , flag = -1 ; ; ++ j , flag *= -1 ) {
        if ( k * t1[j] <= n ) add ( ans , flag * p[n - k * t1[j]] ) ;
        else break ;
        if ( k * t2[j] <= n ) add ( ans , flag * p[n - k * t2[j]] ) ;
        else break ;
    }
    printf ( "%d\n" , ans ) ;
}

int main () {
    int T ;
    preprocess () ;
    scanf ( "%d" , &T ) ;
    for ( int i = 1 ; i <= T ; ++ i ) {
        solve () ;
    }
    return 0 ;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值