传送门
dp好题。
我认为原题的描述已经很清楚了:
你有一个大小为n的背包,你有n种物品,第i种物品的大小为i,且有i个,求装满这个背包的方案数有多少。
两种方案不同当且仅当存在至少一个数i满足第i种物品使用的数量不同。
然而我只会O(n2)O(n^2)O(n2)的做法。
然后通过搜题解学会了O(n∗sqrt(n))O(n*sqrt(n))O(n∗sqrt(n))的做法。
简单讲讲。
首先我们需要分布考虑。
对于大于sqrt(n)sqrt(n)sqrt(n)的物品是选不完的,相当于没有数量限制。
而小于sqrt(n)sqrt(n)sqrt(n)的物品是有数量限制的。
但是直接上多重背包效率上天。
因此我们开始考虑如何优化。
先想想dp式子。
f[i][j]=∑f[i−1][j−k∗i]f[i][j]=\sum f[i-1][j-k*i]f[i][j]=∑f[i−1][j−k∗i]
这启发我们按照余数分组维护f[i−1][jf[i-1][jf[i−1][j modmodmod i]i]i]的前缀和来进行优化。
这样前半部分效率是O(n∗sqrt(n))O(n*sqrt(n))O(n∗sqrt(n))的。
继续考虑后半部分如何维护。
后半部分是一个特殊的完全背包问题。
我们可以想象把所有物品都放在序列上。
那么现在有两种更新方式。
- 我们把所有物品向后移一个位置
- 在队首插入一个最小的数sqrt(n)+1sqrt(n)+1sqrt(n)+1
这就是一种动态转移的思想,这个方法的正确性在于这两种更新方式能够考虑到当前维护所有的序列状态并借此转移出后面的状态。
只是妙不可言。
代码:
#include<bits/stdc++.h>
#define N 100005
#define mod 23333333
#define ll long long
using namespace std;
int n,m,tmp,f[2][N],g[320][N],sum[N];
ll sumg[N],ans=0;
int main(){
scanf("%d",&n),m=sqrt(n),f[0][0]=g[0][0]=sumg[0]=1;
for(int i=1;i<=m;++i){
memset(sum,0,sizeof(sum));
int mod_cnt=-1;
tmp^=1;
for(int j=0;j<=n;++j){
++mod_cnt;
if(mod_cnt==i)mod_cnt=0;
f[tmp][j]=((sum[mod_cnt]+=f[tmp^1][j])%=mod);
if(j>=i*i)(sum[mod_cnt]+=mod-f[tmp^1][j-i*i])%=mod;
}
}
for(int i=0;i<=m;++i)
for(int j=0;j<=n;++j){
if(i&&i+j<=n)(g[i][i+j]+=g[i][j])%=mod;
if(j+m+1<=n)(g[i+1][j+m+1]+=g[i][j])%=mod;
}
for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)(sumg[i]+=g[j][i])%=mod;
for(int i=0;i<=n;++i)(ans+=1ll*f[tmp][i]*sumg[n-i]%mod)%=mod;
cout<<ans;
return 0;
}