51nod #13 D 【DP】【生成函数】

题目大意:有n种物品,第i种物品的大小为i,且有i个,求装满大小为n背包的方案数

贴题解:

首先我们可以发现,令 S=n ,那么对于大小大于S的物品,其实是用不完的,我们可以把他们的数量视为无限个
对于大小小于S的物品,我们可以令f[i][j]表示考虑了前i个物品,总大小为j的方案数,那么有: f[i][j]=ik=0f[i1][jki]
我们在DP的时候,假设当前要计算f[i][j],可以设tmp[v]为当前满足t mod i=v的f[i-1][t]的和
然后就可以通过维护tmp数组,轻松计算出f[i][j]了
这一步的时间复杂度是 O(nn)
具体实现如下:

f[0][0]=1;
for(int i=1;i<=S;i++){
    for(int j=0;j<i;j++)tmp[j]=0;
    int nowmod=-1;
    //nowmod记录的是当前j mod i的值
    for(int j=0;j<=n;j++){
        nowmod++;
        if(nowmod>=i)nowmod=0;
        //维护nowmod
        tmp[nowmod]=tmp[nowmod]+f[i-1][j];
        f[i][j]=tmp[nowmod];
        //计算f[i][j],同时维护tmp
        if(j-i*i>=0)tmp[nowmod]=tmp[nowmod]-f[i-1][j-i*i];
        //由于大小为i的只有i个,所以这里要减掉
    }
}

接下来考虑大小大于S的物品
我们考虑一个给物品“动态添加大小”的DP:
令g[i][j]表示,当前有i个物品,大小总和为j
我们可以做的转移是:
(1):将所有物品的大小加一 :g[i][j]->g[i][j+i]
(2):新建一个大小为S+1的物品g[i][j]->g[i+1][j+S+1]
可以发现,物品总数最多为n/S个,所有g的第一维的规模是n/S的,所以这一个DP也是O(n*sqrt(n))的
于是总复杂度就是O(n*sqrt(n))
这道题还有更加优美的算法,可以用多项式黑科技进行推导,可以得到复杂度O(nlogn)的做法,由于出题人能力有限所以这里就不阐述了

果然有O(nlogn)的做法,好厉害%%%
蒟蒻表示想了好久没有想出来诶。。。QAQ
最后窝只有用生成函数来水了一发。。。

我的做法:

同样是考虑 S=n ,当 i>S 时可以不受限制的取,考虑取第i个物品的生成函数

(1+xi+x2i++xi2)=1xi(i+1)1xi(1+xi+x2i+)=11xii<=Si>S

所以有
F(x)=i=0a(i)xi=i=1n11xii=1S1xi(i+1)

右边前面部分的分母是欧拉函数,有五边形数定理:

i=1(1xi)=i=(1)ixi(3i1)2=i=0(1)ixi(3i±1)2

f(x)=i=0p(i)xi=ni=111xi
(1xx2+x5+x7x12x15+)(1+p(1)x+p(2)x2+)=1
所以有 p(n)p(n1)p(n2)+p(n5)+p(n7)+=0
然后可以递推求得 p(n)
答案 F(x)=f(x)Si=11xi(i+1)
直接暴力乘起来就可以了

时间复杂度 O(nn)

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<ctime>
#define N 100005
#define M 23333333
using namespace std;

int n;
int f[N];

int main()
{
    scanf("%d",&n);
    f[0]=1;
    for (int i=1;i<=n;i++)
        for (int j=1;;j++)
        {
            int k=(3*j*j-j)>>1;
            if (k>i) break;
            if (~j&1) f[i]-=f[i-k];
                else f[i]+=f[i-k];
            if (f[i]>=M) f[i]-=M;
            if (f[i]<0) f[i]+=M;
            k+=j;
            if (k>i) break;
            if (~j&1) f[i]-=f[i-k];
                else f[i]+=f[i-k];
            if (f[i]>=M) f[i]-=M;
            if (f[i]<0) f[i]+=M;
        }
    int S=(int)sqrt(n);
    for (int i=1;i<=S;i++)
    {
        int k=i*i+i;
        for (int j=n;j>=k;j--)
            if ((f[j]-=f[j-k])<0) f[j]+=M;
    }
    cout<<f[n]<<endl;

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值