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);
}