/*
标准的01背包问题。状态转移方程
f[i][v] = max{f[i-1][v-c[i]]+v[i],f[i-1][v]}
*/
#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
int main()
{
int T,N,V,f[1001],vol[1001],val[1001],tem;
cin>>T;
while(T--)
{
cin>>N>>V;
for(int i=0;i<N;i++)
{
cin>>val[i];
}
for(int i=0;i<N;i++)
{
cin>>vol[i];
}
memset(f,0,sizeof(f));
for(int i=0;i<N;i++)
{
for(int j=V;j>=vol[i];j--)
{
tem=f[j-vol[i]]+val[i];
if(f[j]<tem)
f[j]=tem;
}
}
cout<<f[V]<<endl;
}
system("pause");
return 0;
}
Coins
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int dp[100010],w[110],c[110];
int main()
{
int n,m,i,j,k,cnt;
while( scanf("%d%d",&n,&m),n+m )
{
cnt=0;
memset(dp,0,sizeof(dp));
for(i=1;i<=n;i++)
scanf("%d",&w[i]);
for(i=1;i<=n;i++)
scanf("%d",&c[i]);
for(i=1;i<=n;i++)
{
if( c[i]*w[i] > m ) // 完全背包
{
for( j=w[i];j<=m;j++ )
dp[j]= max( dp[j],dp[j-w[i]]+w[i] );
}
//多重背包 转化为 01背包 ,
//把物品的个数 c[i] 拆分为2^0,2^1,……,2^k,c[i]-2^k+1;
//1至c[i]中的数肯定能够分解为前面的数的和。就是十进制转二进制
else
{
k=1;
int num=c[i];
while( k < num )
{
for( j=m; j>=k*w[i] ; j--)
dp[j]=max( dp[j],dp[j-k*w[i]]+k*w[i]); //拆分是对c[i]来说的,
//ij具体操作时其实是把2^k个物品组合为一个物品来操作
num-=k;
k<<=1;
}
for( j=m;j>=num*w[i];j--)
dp[j]=max( dp[j],dp[j-num*w[i]]+num*w[i] );
}
}
for(i=1;i<=m;i++) if( dp[i]==i ) cnt++;
printf("%d\n",cnt);
}
return 0;
}
Ahui Writes Word
/*
题目大意:
Ahui写作文,每个单词都有复杂度和value,先输入N,C,代表整篇作文有n个单词,
然后作文的最大复杂度小于V。(1 ≤ N ≤ 100000, 1 ≤ C ≤ 10000),后面跟N行,
每行输入单词,还有单词的价值val跟单词的复杂度compl
(0<=val<=com<=10),求在单词的复杂度不超过C的情况下,Ahui能够获得的最大价值。
解题思路:
一般的01背包会TLE,因为N * C = 1000000000;但是仔细考虑下,
其实每个单词v跟c都小于等于10,所以这么大的输入量,必定有单词的价值跟复杂度是完全相同的,
因为10*10=100;因此可以考虑用多重背包减小时间复杂度。
P03: 多重背包问题
题目:有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],
价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,
且价值总和最大。
基本算法:这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,
因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。
令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,
则有状态转移方程:f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}
复杂度是O(V*Σn[i])。
转化为01背包问题
另一种好想好写的基本方法是转化为01背包求解:把第i种物品换成n[i]件01背包中的物品,
则得到了物品数为Σn[i]的01背包问题,直接求解,复杂度仍然是O(V*Σn[i])。
但是我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。
仍然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,
使得原问题中第i种物品可取的每种策略——取0..n[i]件——
均能等价于取若干件代换以后的物品。另外,取超过n[i]件的策略必不能出现。
方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,
这件物品的费用和价值均是原来的费用和价值乘以这个系数。
使这些系数分别为1,2,4,...,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。
例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。
分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。
另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示,
这个证明可以分0..2^k-1和2^k..n[i]两段来分别讨论得出,并不难,希望你自己思考尝试一下。
这样就将第i种物品分成了O(log n[i])种物品,
将原问题转化为了复杂度为<math>O(V*Σlog n[i])的01背包问题,是很大的改进。
下面给出O(log amount)时间处理一件多重背包中物品的过程,其中amount表示物品的数量:
procedure MultiplePack(cost,weight,amount)
if cost*amount>=V
CompletePack(cost,weight)
return
integer k=1
while k<amount
ZeroOnePack(k*cost,k*weight)
amount=amount-k
k=k*2
ZeroOnePack(amount*cost,amount*weight)
希望你仔细体会这个伪代码,如果不太理解的话,不妨翻译成程序代码以后,
单步执行几次,或者头脑加纸笔模拟一下,也许就会慢慢理解了。
O(VN)的算法
多重背包问题同样有O(VN)的算法。这个算法基于基本算法的状态转移方程,
但应用单调队列的方法使每个状态的值可以以均摊O(1)的时间求解。
由于用单调队列优化的DP已超出了NOIP的范围,故本文不再展开讲解。
我最初了解到这个方法是在楼天成的“男人八题”幻灯片上。
小结
这里我们看到了将一个算法的复杂度由O(V*Σn[i])改进到O(V*Σlog n[i])的过程,
还知道了存在应用超出NOIP范围的知识的O(VN)算法。希望你特别注意“拆分物品”的思想和方法,
自己证明一下它的正确性,并将完整的程序代码写出来。
*/
#include<stdio.h>
#include<string.h>
char ch[100] ;
int nkind , total_comp ;
int value[100024] , comp[100024] , fine[100024] , map[11][11];
int main ()
{
int pos , x , y ,sum , t ;
while ( scanf ( "%d%d" , &nkind , &total_comp ) != EOF )
{
memset( map , 0 , sizeof (map) ) ;
for ( int i = 0 ; i < nkind ; i ++ )
{
scanf ( "%s%d%d" , ch , &x , &y ) ;
map[x][y] ++ ;
}
pos = 0 ; // 用二分制转换成01背包
for ( int i = 0 ; i <= 10 ; i ++ )
for ( int j = 0 ; j <= 10 ; j ++ )
{
if( map[i][j] >= 1 )
{
sum = 1 , t = 1 ;
while ( sum <= map[i][j] )
{
value[pos] = i * t ;
comp[pos++] = j * t ;
t *= 2 ;
sum += t ;
}
if ( ( sum - t ) < map[i][j] )
{
value[pos] = i * (map[i][j]-sum + t) ;
comp[pos++] = j * (map[i][j]-sum + t) ;
}
}
}
memset( fine , 0 , sizeof (fine) ) ;
for ( int i = 0 ; i < pos ; i ++ )
for ( int j = total_comp ; j >= comp[i] ; -- j )
if ( fine[j] < fine[j-comp[i]] + value[i] )
fine[j] = fine[j-comp[i]] + value[i] ;
printf ( "%d\n" , fine[total_comp] ) ;
}
return 0 ;
}
Robberies
/*
状态转移方程是:dp[j]=max(dp[j],dp[j-m[i]]*(1 - q[i]))
其中,dp[j]表示抢j块大洋的最大的逃脱概率,条件是dp[j-m[i]]可达,也就是之前抢劫过;
始化为:dp[0]=1,其余初始化为-1.
*/
#include<iostream>
using namespace std;
double max(double a,double b)
{
return a>b?a:b;
}
int main()
{
int t;
cin>>t;
for(int x =1; x <= t; x++)
{
double p;
cin>>p;
int n;
cin>>n;
int i,j;
int* m = new int[n+1];
double * q = new double[n+1];
int s = 0;
for(i = 1; i <= n; i++)
{
cin>>m[i]>>q[i];
s += m[i];
}
double *dp = new double[s+1];
dp[0] = 1;
for(i = 1; i <= n; i++)
for(j = s; j >= m[i]; j--)
{
dp[j] = max(dp[j],dp[j - m[i]]*(1-q[i]));
}
for(i = s; i >= 0; i--)
if(dp[i] >= 1-p)
{
cout<<i<<endl;
break;
}
}
}
Watch The Movie
#include<cstdio>
#include<cstring>
#define max(a,b) a>b?a:b
#define inf 0x7fffffff
int N,M,L,t[105],v[105],dp[105][1005];
int main()
{
int T,i,j,k;
scanf("%d",&T);
while(T--)
{
//想买N部(即物品数量),最多卖M部(背包容量),最大时限为L
//每部耗时t[i],价值v[i]
//求在不超过最大时限L,且看完所有M部电影 的情况下获得的最大价值
//第一层背包:时间背包; 第二层背包:数量背包
scanf("%d%d%d",&N,&M,&L);
for(i=0;i<N;i++)
scanf("%d%d",&t[i],&v[i]);
//初始化dp
for(i=0;i<=M;i++)
for(j=0;j<=L;j++)
{ if(i==0) dp[i][j]=0;
else dp[i][j]=-inf;
}
//二维背包dp
for(j=0;j<N;j++) //注意枚举N个物品的变量必须放在最外层
for(k=L;k>=t[j];k--)
for(i=1;i<=M;i++)
dp[i][k]=max(dp[i][k],dp[i-1][k-t[j]]+v[j]);
//判断
if(dp[M][L]>0)printf("%d\n",dp[M][L]);
else printf("0\n");
}
}
饭卡
/*
这是一个变形的01背包问题,首先如果金额小于5元,剩余金额不变,为已有金额。如果大于等于5元
我们先用5元买最贵的菜。然后用剩下的钱买其他的菜这时就是一个典型的01背包问题了;
求出最大的花费,然后用总金额减去最大的花费即为剩余金额。
抽象的地方是花费和价值都是用金额表示。
*/
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
bool cmp(int a,int b)
{
return a<b;
}
int main()
{
int n,k[1001],f[1001],m;
while(scanf("%d",&n),n)
{
for(int i = 0; i < n; i++)
{
scanf("%d",&k[i]);
}
sort(k,k+n,cmp);
scanf("%d",&m);
if(m>=5)
{
memset(f,0,sizeof(f));
for(int i = 0; i < n-1; i++)
for(int j = m-5; j>=k[i];j--)
{
if(f[j-k[i]]+k[i]>f[j]&&f[j-k[i]]+k[i]<=m-5)//注意这里的f[j-k[i]]+k[i]<=m-5。
f[j] = f[j-k[i]]+k[i];
}
printf("%d\n",m-f[m-5]-k[n-1]);
}
else printf("%d\n",m);
}
return 0;
}
Watch The Movie
#include<cstdio>
#include<cstring>
#define max(a,b) a>b?a:b
#define inf 0x7fffffff
int N,M,L,t[105],v[105],dp[105][1005];
int main()
{
int T,i,j,k;
scanf("%d",&T);
while(T--)
{
//想买N部(即物品数量),最多卖M部(背包容量),最大时限为L
//每部耗时t[i],价值v[i]
//求在不超过最大时限L,且看完所有M部电影 的情况下获得的最大价值
//第一层背包:时间背包; 第二层背包:数量背包
scanf("%d%d%d",&N,&M,&L);
for(i=0;i<N;i++)
scanf("%d%d",&t[i],&v[i]);
//初始化dp
for(i=0;i<=M;i++)
for(j=0;j<=L;j++)
{ if(i==0) dp[i][j]=0;
else dp[i][j]=-inf;
}
//二维背包dp
for(j=0;j<N;j++) //注意枚举N个物品的变量必须放在最外层
for(k=L;k>=t[j];k--)
for(i=1;i<=M;i++)
dp[i][k]=max(dp[i][k],dp[i-1][k-t[j]]+v[j]);
//判断
if(dp[M][L]>0)printf("%d\n",dp[M][L]);
else printf("0\n");
}
}
Proud Merchants
/*
按照q-p从小到大排序,然后01背包。
至于按照q-p从小到大排序比较难想到。q-p其实就是不更新的范围,
不更新的范围从小到大递增时就不会影响后面的DP了。
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
using namespace std;
const int MAXN=550;
struct Node
{
int p,q,v;
}node[MAXN];
int dp[5500];
bool cmp(Node a,Node b)//按照 q-p 从小到大排序
{
return a.q-a.p < b.q-b.p;
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=0;i<n;i++)
scanf("%d%d%d",&node[i].p,&node[i].q,&node[i].v);
sort(node,node+n,cmp);
memset(dp,0,sizeof(dp));
for(int i=0;i<n;i++)
for(int j=m;j>=node[i].q;j--)
dp[j]=max(dp[j],dp[j-node[i].p]+node[i].v);
printf("%d\n",dp[m]);
}
return 0;
}