您的位置:首页 > 其它

动态规划的两个经典问题--01背包

2009-09-27 22:01 471 查看
"学动态规划要从经典问题开始"
--By lrj

答应了小Q要写这个的.......哎......

先说那个什么01背包.....
为什么叫背包呢?因为这些问题的原型是说有很多东西,每个东西都有体积,也有价值,你要去春游,要选一些东西放到背包里.问题是要怎么选才能在总体积不超过背包的体积的前提下,让总价值最大.
为什么是0/1呢?因为每种东西都不一样(每个东西的体积和价值都不一样),你要拿就拿,不拿拉到,就2种选择,不能拿半个,也不能拿2个(因为每种东西只有一个)...所以叫01.
(其实背包问题不一定只能拿一个,这里说的是背包问题的一种--01背包).


所以,0/1背包其实就是决定每个东西拿不拿了.
那么,"拿"这个动词怎么表示呢?不是你把手伸出去伸回来就完了.要考虑在算法里面"拿"的意义.一旦"拿"了一个东西,那么,总的体积会增加这个东西的体积,总的价值也会增加这个东西的价值.
用f[k]表示当总体积为k的时候物品的总价值.
如果不拿这个东西,f[k]就等于f[k],如果要拿这个东西.....怎么办呢?总的体积k是确定的,那么拿了这个东西过后的总价值等于拿这个东西之前的总价值加上这个东西的价值,拿这个东西之前的总价值是多少呢?既然拿了过后总体积是k,那么拿之前的体积就是(k-拿的物品的体积),所以拿之前的价值是f[k-当前物品的体积],所以"拿"这个动作,就表现为:f[k]=f[k-当前物品的体积]+当前物品的价值.而要最后最大,所以~~~~就选2者中最大的.....
也就是:f[k]=max{f[k],f[k-当前物品的体积]+当前物品的价值}
OK了~~~~~~然后每种物品枚举一次....
在写的时候,有一些细节,就是计算f[k]的时候,要从大到小循环回来...为什么呢?
比如,设循环的时候k取过k1,k2,k3三个值,物品中有一个体积为v1的物品,而且k3-k2=k2-k1=v1,如果从小到大循环,在计算的时候就会出现"f[k2]是由f[k1]加上体积为v1的物品的价值得到,而f[k3]又是由f[k2]加上体积为v1的物品的价值得到",这就是说--这个体积为v1的物品被取了2次!!!!所以不行咯~~~~~~

0/1背包在noip中有这些题:noip2005普及组第3题"采药"(这个题是原封不动的0/1背包),noip2006普及组第二题"开心的金明" (几乎原封不动),noip2001普及组"装箱问题"(这个也可以看作背包的,只是小变形了一下),noip2006提高组第二题"金明的预算方案" (很BT的变形,这里略去)

noip2005普及组第3题"采药":http://www.vijos.cn/Problem_Show.asp?id=1104
完全不需要修改的0/1背包:把采药时间看作体积,药的价值为价值.
我的c++程序(屏幕输入输出):
#include<iostream>
#include<cstring>
using namespace std;

int max(int a,int b)
{
if(a>b)return a;
else return b;
}

int main()
{
int t,m;
scanf("%d %d",&t,&m);
int *c=new int[t+1];//开一个数组,在c语言中可以用malloc函数代替
for(int k=0;k<=t;k++)c[k]=0;

class medic//这里在c语言中可以用结构体代替:把"class"改成"struct",去掉"public:"
{
public:
int value;
int time;
}*me;

me=new medic[m];
for(int k=0;k<m;k++)
{
scanf("%d %d",&me[k].time,&me[k].value);
}
for(int j=0;j<m;j++)//枚举药的种类
{
for(int k=t;k>=me[j].time;k--)/*计算,注意要k>=me[j].time才能保证k-me[j].time>0 */
{
c[k]=max(c[k],c[k-me[j].time]+me[j].value);//套前面那个公式
}
}
int max=0;
for(int k=0;k<=t;k++)/*这里不用这么写,直接输出c[t]就可以了,我写的时候受hyf牛的影响写复杂了 */
{if(max<=c[k])max=c[k];
}
cout<<max;
getchar();getchar();
return 0;
}


noip2006普及组第二题"开心的金明":http://www.vijos.cn/Problem_Show.asp?id=1317
只需要先计算出价格和重要度的乘积作为价值就好了.
我的c++程序:
#include<cstdio>
#include<cstring>

int main()
{
int c,m;
scanf("%d %d",&c,&m);

class object
{
public:
int value;
int cost;
};

long long *money=new long long [c+1];

for(int k=0;k<=c;k++)money[k]=0;

object *obj;
obj=new object [m];

for(int k=0;k<m;k++)
{
scanf("%d %d",&obj[k].cost,&obj[k].value);
}

for(int k=0;k<m;k++)//先计算乘积
{
obj[k].value*=obj[k].cost;
}

for(int j=0;j<m;j++)
{
for(int k=c;k>=obj[j].cost;k--)
{
if(money[k]<money[k-obj[j].cost]+obj[j].value)
money[k]=money[k-obj[j].cost]+obj[j].value;
}
}

printf("%d",money[c]);//这样输出就够了~~

return 0;
}


noip2001普及组"装箱问题":http://www.vijos.cn/Problem_Show.asp?id=1133
这个题我本来用的枚举搜索过了,其实也可以看作背包问题的一个小变形
没有了价值,变成了 f[k]=max{f[k],f[k-当前物品的体积]+当前物品的体积}
可以想成每个东西的价值就等于它的体积.
我的c++程序:
#include<iostream>
#include<cstring>

using namespace std;

int main()
{
int v,n;
scanf("%d",&v);
scanf("%d",&n);
int *a=new int
;
for(int k=0;k<n;k++)
{
scanf("%d",&a[k]);
}
int *b=new int [v+1];

for(int k=0;k<=v;k++)
{
b[k]=0;
}

for(int j=0;j<n;j++)
{
for(int k=v;k>=a[j];k--)
{
if(b[k]<(b[k-a[j]]+a[j])&&(b[k-a[j]]+a[j])<=v)/*就这儿有点小变化*/
b[k]=b[k-a[j]]+a[j];
}
}

printf("%d",v-b[v]);
system("pause");
return 0;
}

就说这么多了....
还是lrj的一句话"动态规划真的需要思维很灵活,需要灵感"
我的思维就不灵活,也没灵感...
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: