2018.09.25 51nod1597 有限背包计数问题(背包+前缀和优化)

传送门
dp好题。
我认为原题的描述已经很清楚了:
你有一个大小为n的背包,你有n种物品,第i种物品的大小为i,且有i个,求装满这个背包的方案数有多少。
两种方案不同当且仅当存在至少一个数i满足第i种物品使用的数量不同。


然而我只会 O ( n 2 ) O(n^2) O(n2)的做法。
然后通过搜题解学会了 O ( n ∗ s q r t ( n ) ) O(n*sqrt(n)) O(nsqrt(n))的做法。
简单讲讲。

首先我们需要分布考虑。
对于大于 s q r t ( n ) sqrt(n) sqrt(n)的物品是选不完的,相当于没有数量限制。
而小于 s q r t ( 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[i1][jki]
这启发我们按照余数分组维护 f [ i − 1 ] [ j f[i-1][j f[i1][j m o d mod mod i ] i] i]的前缀和来进行优化。
这样前半部分效率是 O ( n ∗ s q r t ( n ) ) O(n*sqrt(n)) O(nsqrt(n))的。
继续考虑后半部分如何维护。
后半部分是一个特殊的完全背包问题。
我们可以想象把所有物品都放在序列上。
那么现在有两种更新方式。

  1. 我们把所有物品向后移一个位置
  2. 在队首插入一个最小的数 s q r t ( n ) + 1 sqrt(n)+1 sqrt(n)+1

这就是一种动态转移的思想,这个方法的正确性在于这两种更新方式能够考虑到当前维护所有的序列状态并借此转移出后面的状态。
只是妙不可言。
代码:

#include 
#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;
}

你可能感兴趣的:(#,dp,#,背包)