51Nod 1597 有限背包计数问题

首先这是一个多重背包

但是它的数据非常特殊,我们可以利用其性质优化算法

一个显然的优化是
\(i>\sqrt n\)时,可以取消个数限制
\(f[i][j]\)表示选了\(i\)个物品,体积为\(j\)的方案数
一共有两种转移
可以由\(i-1\)个物品加上一个最小的物品\(\sqrt n+1\)
可以由\(i\)个物品全部加一(全部换成下一个)
\(f[i][j]=f[i-1][j-(\sqrt n+1)]+f[i][j-i]\)
考虑得到一个答案的过程
对于每一个过程都会得到一个合法的答案
对于每一个合法的答案都有唯一一个过程

因为对于每一个方案
如果其中存在最小的物品
那么一定是由第一种方案转移
否则一定是由第二种方案转移

比如\(n=16\)

体积为\(16\)的一种方案是\(5+5+6\)
这个状态一定由\(5+6\)转移来
因为\(4=\sqrt 16\),比最小的物品还小
所以\(4+4+5\)是不可能的

体积为\(12\)的一种方案是\(6+6\)
这个状态一定由\(5+5\)转移来
因为方案里并没有\(5\)(最小的物品)

所以方案与过程一一对应
复杂度\(O(n\sqrt n)\)

剩下的是多重背包
\(f[i][j]=\sum_{k=0}^{i}{f[i-1][j-i*k]}\)
按模\(i\)分类
复杂度\(O(n\sqrt n)\)

最后枚举多少空间给前\(\sqrt n\)个物品(剩下空间给其它物品)
统计答案即可
复杂度\(O(n\sqrt n)\)

#include<bits/stdc++.h>

using namespace std;

#define gc c=getchar()
#define r(x) read(x)

template<typename T>
inline void read(T&x){
    x=0;T k=1;char gc;
    while(!isdigit(c)){if(c=='-')k=-1;gc;}
    while(isdigit(c)){x=x*10+c-'0';gc;}x*=k;
}

const int p=23333333;
const int N=1e5+7;

int sum[N],f1[2][N],f2[2][N];

inline int add(int a,int b){
	a+=b;
	if(a>=p)a-=p;
	return a;
}

int main(){
	int n;r(n);
	int t=sqrt(n);
	
	f1[0][0]=1;
	for(int i=1;i<=t;++i){
		for(int j=0;j<=n;++j){
			sum[j%i]=add(sum[j%i],f1[i&1^1][j]);
			if(j-i*(i+1)>=j%i)sum[j%i]=add(sum[j%i],p-f1[i&1^1][j-i*(i+1)]);
			f1[i&1][j]=sum[j%i];
		}
		memset(sum,0,i<<2);
	}
	
	memset(sum,0,(n+1)<<2);
	f2[0][0]=1;
	for(int i=1;i<=t;++i){
		memset(f2[i&1]+(i-1)*(t+1),0,(t+1)<<2);
		for(int j=i*(t+1);j<=n;++j){
			f2[i&1][j]=add(f2[i&1][j-i],f2[i&1^1][j-(t+1)]);
			sum[j]=add(sum[j],f2[i&1][j]);
		}
	}
	
	sum[0]=1;
	long long ans=0;
	for(int i=0;i<=n;++i)(ans+=1ll*f1[t&1][i]*sum[n-i])%=p;
	
	printf("%lld\n",ans);
}

posted @ 2018-10-14 21:41  NamelessOIer  阅读(225)  评论(0编辑  收藏  举报