题目大意:有n种物品,第i种物品的大小为i,且有i个,求装满大小为n背包的方案数
贴题解:
首先我们可以发现,令
S=n√
,那么对于大小大于S的物品,其实是用不完的,我们可以把他们的数量视为无限个
对于大小小于S的物品,我们可以令f[i][j]表示考虑了前i个物品,总大小为j的方案数,那么有:
f[i][j]=∑ik=0f[i−1][j−k∗i]
我们在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个物品的生成函数
所以有
右边前面部分的分母是欧拉函数,有五边形数定理:
设 f(x)=∑∞i=0p(i)xi=∏ni=111−xi
有 (1−x−x2+x5+x7−x12−x15+⋯)(1+p(1)x+p(2)x2+⋯)=1
所以有 p(n)−p(n−1)−p(n−2)+p(n−5)+p(n−7)+⋯=0
然后可以递推求得 p(n)
答案 F(x)=f(x)∏Si=11−xi(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;
}