[POI2005]Bank notes 【多重背包】
本人水平有限,题解不到为处,请多多谅解
本蒟蒻谢谢大家观看
题目:传送门
第一眼是多重背包,我们用多重背包的模板可以套一下,发现只会TLE三个点
直接拆分法的多重背包如下:
1 #include<bits/stdc++.h> 2 #pragma GCC optimize(3) 3 const int N=1e5+10; 4 using namespace std; 5 int n,m; 6 int b[N],c[N]; 7 int f[N]; 8 void inint(){ 9 freopen("bank.in","r",stdin); 10 freopen("bank.out","w",stdout); 11 } 12 inline int read(){ 13 int x=0,f=1;char ch=getchar(); 14 while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} 15 while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} 16 return x*f; 17 } 18 inline void write(int x) 19 { 20 if(x<0)x=-x,putchar('-'); 21 if(x>9)write(x/10); 22 putchar(x%10+'0'); 23 } 24 int main() 25 { 26 //inint(); 27 memset(f,0x3f,sizeof(f)); 28 f[0]=0; 29 n=read(); 30 for(int i=1;i<=n;i++){ 31 b[i]=read(); 32 } 33 for(int i=1;i<=n;i++){ 34 c[i]=read(); 35 } 36 m=read(); 37 for(int i=1;i<=n;i++){ 38 for(int j=m;j>=b[i];j--){ 39 for(int k=0;k<=c[i];k++){ 40 if(k*b[i]>j)break; 41 f[j]=min(f[j],f[j-k*b[i]]+k); 42 } 43 } 44 } 45 printf("%d\n",f[m]); 46 return 0; 47 }
但是很奇怪,用C++交题却可以AC,一转到C++11(NOI)交会TLE,可能是编译器不同吧。
正解是用二进制拆分法或单调队列优化法。
先介绍二进制拆分法:
不断把个数ci用2的几次方表示,可以把维度降到O(logci)个,效率较高。
具体证明参照《算法竞赛指南》李煜东 这本书。
例如:2^0为一组,2^1为一组,2^2为一组,2^3为一组,……
再把这些组分别进行01背包即可
code:
1 #include<bits/stdc++.h> 2 #pragma GCC optimize(3) 3 const int N=1e5+10; 4 using namespace std; 5 int n,k; 6 int b[N],c[N],f[N]; 7 void inint(){ 8 freopen("bank.in","r",stdin); 9 freopen("bank.out","w",stdout); 10 } 11 inline int read(){ 12 int x=0,f=1;char ch=getchar(); 13 while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} 14 while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} 15 return x*f; 16 } 17 inline void write(int x) 18 { 19 if(x<0)x=-x,putchar('-'); 20 if(x>9)write(x/10); 21 putchar(x%10+'0'); 22 } 23 void dp(int val,int num){ 24 for(int i=k;i>=val;i--){ 25 f[i]=min(f[i],f[i-val]+num);//基本01背包 26 } 27 } 28 int main() 29 { 30 //inint(); 31 n=read(); 32 memset(f,0x3f,sizeof(f)); 33 f[0]=0; 34 for(int i=1;i<=n;i++)b[i]=read(); 35 for(int i=1;i<=n;i++)c[i]=read(); 36 k=read(); 37 for(int i=1;i<=n;i++){ 38 for(int j=1;j<=c[i];j<<=1){//二进制拆分法 39 dp(b[i]*j,j); 40 c[i]-=j;//看是否还有剩余 41 } 42 if(c[i])dp(b[i]*c[i],c[i]);//把剩余的个数分为一组进行背包 43 } 44 printf("%d\n",f[k]); 45 return 0; 46 }
单调队列优化DP
使用单调队列优化可以使O(M*sigma(N)*ci)降到O(NM),与01背包中DP算法效率基本相同
code:
1 #include<bits/stdc++.h> 2 #define N 205 3 #define M 20005 4 #define inf 0x3f3f3f3f 5 using namespace std; 6 int b[N],c[N],f[M],q[M],w[M]; 7 void inint(){ 8 freopen("bank.in","r",stdin); 9 freopen("bank.out","w",stdout); 10 } 11 int main() 12 { 13 //inint(); 14 int n,m; 15 scanf("%d",&n); 16 for (int i=1;i<=n;i++) 17 scanf("%d",&b[i]); 18 for (int i=1;i<=n;i++) 19 scanf("%d",&c[i]); 20 scanf("%d",&m); 21 memset(f,inf,sizeof(f)); 22 f[0]=0; 23 for (int i=1;i<=n;i++) 24 { 25 for (int j=0;j<b[i];j++) 26 { 27 int head=1,tail=0; 28 for (int k=j;k<=m;k+=b[i]) 29 { 30 while (head<=tail&&w[head]<k-c[i]*b[i]) head++; 31 while (head<=tail&&f[k]-(k-w[head])/b[i]<=q[tail]) tail--; 32 q[++tail]=f[k]; 33 w[tail]=k; 34 f[k]=min(f[k],q[head]+(k-w[head])/b[i]); 35 } 36 } 37 } 38 printf("%d",f[m]); 39 return 0; 40 }