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

来源:互联网 发布:淘宝卖家客服人工服务 编辑:程序博客网 时间:2024/05/29 14:06

题目大意:有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 23333333using 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 0