现在的位置: 首页 > 综合 > 正文

01背包入门题解–poj3624、poj3628、hdu1864、poj3211

2017年02月22日 ⁄ 综合 ⁄ 共 5733字 ⁄ 字号 评论关闭

通过之前的博文《白话01背包》对01背包学习进行总结,几个01背包入门题解,给出顺序为难度梯度:

poj3624Charm Bracelet

完全模板题

这里给出 一维和二维的三份代码...为什么有三份....??对于二维的,有两份,一维的一份.....

二维第一份:

#include<stdio.h>
#include<string.h>

#define MAX1 3410
#define MAX2 12885

int main()
{
	int i,j;
	int N,M;
	int w[MAX1],va[MAX1],dp[MAX1][MAX2];
	scanf("%d%d",&N,&M);
	
	for(i=1;i<=N;i++)
		scanf("%d%d",&w[i],&va[i]);
	
	//dp过程
	memset(dp,0,sizeof(dp));
	for(i=1;i<=N;i++)
		for(j=1;j<=M;j++)//注意这里j表示背包的容量为j 这里是从1到M递增,第二份代码中将递减
		{
			if(w[i]<=j)
			{
				if(dp[i-1][j]<dp[i-1][j-w[i]]+va[i])
					dp[i][j]=dp[i-1][j-w[i]]+va[i];
				else
					dp[i][j]=dp[i-1][j];
			}
			else
				dp[i][j]=dp[i-1][j];
		}
	printf("%d\n",dp[N][M]);
	return 0;
}

二维第二份:只给出dp过程...其他的一样

//dp过程
memset(dp,0,sizeof(dp));
for(i=1;i<=N;i++)
        for(j=M;j>=w[i];j--)//注意这里j表示背包的容量为j 这里和第一份不同的就是j只 从M递减到w[i] 作用是一样的
		if(dp[i-1][j]<dp[i-1][j-w[i]]+va[i])
			dp[i][j]=dp[i-1][j-w[i]]+va[i];
		else
			dp[i][j]=dp[i-1][j];

下面是一维的状态转移方程

dp[j]=max(dp[j],dp[j-w[i]]+va[i]);解释的意思是一样的...但是为什么可以这样呢..--《白话01背包》

#include<stdio.h>

#define MAX 12881

int dp[MAX];
int w[MAX],va[MAX];

int main()
{
	int N,M;
	int i,j;
	scanf("%d%d",&N,&M);
	for(i=1;i<=N;i++)
		scanf("%d%d",&w[i],&va[i]);

	for(i=1;i<=N;i++)
		for(j=M;j>=jewelry[i][0];j--)
			if(dp[j]<dp[j-w[i]]+va[i])
				dp[j]=dp[j-w[i]]+va[i];
	printf("%d\n",dp[M]);
	return 0;
}

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

poj3628Bookshelf 2 牛的书架

分析:(很是重要呀...)

首先,这是一个比较简单的01背包问题...你可以把这样想:对于这个”背包“,我们要选的”物品“(牛),将它们的“价值”和“重量”都看成相等的,即牛的高度,然后就是确定什么作为背包... 哎哎不知道有没有人和我一样,最开始一直在想,把架子的高度作为”背包“,怎么老是WA.....请看这句话 This total height must be no less than the height of the bookshelf in order for
the cows to reach the top. 其实吧....题意不是在不超过梯子的高度下选几头牛让其高度最大....而是选择几头牛,在所有的组合超过梯子高度的情况下选择超过的最少的那一个方案.....

于是..这样的话那就可以把所有的牛的高度加起来 的sum作为背包容量....然后dp一次...

状态转移方程为:

dp[j]=max(dp[j],dp[j-h[i]]+h[i]);表示把第i头牛跌起来的最大高度,h[i]表示第i头牛的高度 

要注意的是,最后的dp[sum]并不是最终的选择,因为这里是要选择超过梯子的最小高度...

那么最后可以用一个for循环找一次

#include <stdio.h>
#include <string.h>

int dp[1000005],a[22];

int max(int a,int b)
{
    return a>b?a:b;
}

int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        int i,j,sum = 0;
        memset(dp,0,sizeof(dp));
        memset(a,0,sizeof(a));
        for(i = 1; i<=n; i++)
        {
            scanf("%d",&a[i]);
            sum+=a[i];
        }
        for(i = 1; i<=n; i++)
            for(j = sum; j>=a[i]; j--)
                dp[j] = max(dp[j],dp[j-a[i]]+a[i]);
        for(i = 1; i<=sum; i++)//这里就是找超过梯子高度最下的那个
        {
            if(dp[i]>=m)
            {
                printf("%d\n",dp[i]-m);//答案是差值
                break;
            }
        }
    }

    return 0;
}

然后还有就是可以用dfs,不过多解释...给出代码

#include<stdio.h>

#define MAX 20
#define IF 20000005

int N,B;
int sum;
int ans;
int cow[MAX];

void dfs(int n,int va)
{
	if(n == N)
	{
		if(va>=B && va<ans)ans=va;
		return;
	}
	dfs(n+1,va);//这里实际是枚举每次要不放、要不不放牛的情况...
	dfs(n+1,va+cow[n]);
}

int main()
{
	while(~scanf("%d%d",&N,&B))
	{
		sum=0;
		ans=IF;
		for(int i=0;i<N;i++)
			scanf("%d",&cow[i]),sum+=cow[i];
		dfs(0,0);
		printf("%d\n",ans-B);
	}
	return 0;
}

----------------------------------------------------------------------------------------------------------------------------

hdu1864最大报销额

相信看到题目的人都大概知道要用01背包解决,不过也相信有很多地方很恼人...比如:

题目说:每张发票上,单项物品的价值不得超过600元,其实却是单类物品,意思是你得先把一张发票的每一项分类求和 ,A类sum_A,以及B_sum,C_sum....然后判断条件就是 三者之和不超过1000,每类不超过600,

其次,你若用发票最大报销额 max 作为背包...那么dp[max].....很难表示。因为这是一个double类型的数,强制转换为 int 有可能有误差。....

还有就是,一张发票中出现了别的类型的物品,那么整张发票就不能报销.

所有要注意以上几点。

于是...就看了别人的结题报告....发现有一个很巧妙的手法....在平时..我们dp的过程都是用 背包的容量去正向或者反向dp....在这里...看到前辈们居然。。。。直接上代码...附有详细说明:

还要说明的一点是,这里,我们把每一张能报销的发票看做一个物品..

#include<stdio.h>
#include<string.h>

int N;发票数
double Q;最大报销额

double thing[31];//发票数组
int thing_sum;//统计能报销的发票数
double dp[31];//dp过程中的数组,
double a,b,c;//三类物品的和

void Input()//读入
{
	int m;//每张发票的物品数
	char ch;//物品类
	double pre;//每次物品的价格
	for(int i=0;i<N;i++)
	{
		scanf("%d",&m);

		bool flag=true;//用来判定是不是出现其他类别的物品
		a=b=c=0;
		
		while(m--)
		{
			scanf(" %c:%lf",&ch,&pre);//这里的%c前面有一个空格,这样就按格式输入..不要担心空格换行被ch吸收
			if(ch=='A') a+=pre;
			else if(ch=='B') b+=pre;
			else if(ch=='C') c+=pre;
			else flag=false;    //出现其他种类..标记
		}
		if(a+b+c<=1000 && flag &&(a<=600 && b<=600 && c<=600))//单类不超过600、总和、无其他类物品
			thing[thing_sum++]=a+b+c;
	}
}

void work()//处理
{
	int i,j;
	double max;//临时计算
	double ans=0;//保存结果
	memset(dp,0,sizeof(dp));

	for(i=0;i<thing_sum;i++)//这里还是和平时的dp差不多...
	{
		max=0;
		for(j=0;j<i;j++)//但是这里就有技巧啦。表示,我们取前i-1件物品,挑选其中价值最大的,并且和第i件报销之和不超过最大限制
		{
			if(dp[j]>max && dp[j]+thing[i]<=Q)
				max=dp[j];
		}
		dp[i]=max+thing[i];//在这里dp[i]任然表示前i张发票能报销的最大金额
		if(dp[i]>ans)//在这个过程中,用ans保存最大的...
			ans=dp[i];
	}
	printf("%.2lf\n",ans);//over啦////太神奇啦...
}

int main()
{
	while(scanf("%lf%d",&Q,&N),N)
	{
		Input();
		work();
	}
	return 0;
}

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

poj3211洗衣服

啧啧...觉得一开始应该有很多人难明白题意吧。。。大意:

很多件衣服,给两个情侣洗,值得注意的是衣服可以有很多件,每一件颜色可以相同也可以不同,但是,为了不让衣服相互染色,他们两个必须一起洗完一种颜色的衣服,才可以一起洗另一种颜色的衣服,就算有一个颜色的衣服只有一件,也得让一个洗完,(另一个旁边看着他洗大笑),才可以一起处理下一种颜色,要求让时间最少,

分析:

那么就只能对于每一种颜色去01背包dp一次,比如对于红色衣服有N件,每一件的时间我们知道,那么意思就是要求我们把这N件衣服分给这两个人洗,可以知道,分配的越均匀,那么一起完成的时间就会越少,那么我们可以选择这N件衣服的时间和 sum的 1/2 做为背包,挑出其中几件衣服,让它们的时间和不超过sum/2,并且最接近sum/2 ,那么...对于前面的分析就有 01背包的味道啦。然后 把这dp[sum/2]的衣服时间给女孩洗(出于好男人的选择而已,因为这个值是不超过sum/2
的时间,也就是少的时间),另一个的时间就是sum-dp[sum/2]......(注意这个时间是完成这个颜色的总时间,这个不会有问题吧?)

像这样,把所有的颜色dp一次,保存每次的 sum-dp[sum/2]和...就是答案

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<string>
using namespace std;

#define MAX 101

struct node
{
	int time;
	string color;
	bool operator <(const node &n)const
	{
		if(color==n.color)//只要按照颜色排序就可以
			return time<n.time;
		else
			return color<n.color;
	}
}head[MAX];

int M,N;

void Input()
{
	int i;
	string str;
	for(i=0;i<M;i++)
		cin>>str;
	for(i=0;i<N;i++)
		cin>>head[i].time>>head[i].color;

	sort(head,head+N);
}

void dp()
{
	int ans=0;
	for(int i=0;i<N;)//这里我们用j更新i变量
	{
		int j=i+1;
		int sum=head[i].time;
		while(head[j].color == head[i].color && j<N)//选取颜色相同的物品
		{
			sum+=head[j].time;
			j++;
		}

		//开始一种颜色dp
		int DP[MAX*1000]={0};
		for(int p=i;p<j;p++)
			for(int q=sum/2;q>=head[p].time;q--)
				if(DP[q]<DP[q-head[p].time]+head[p].time)
					DP[q]=DP[q-head[p].time]+head[p].time;
		ans+=sum-DP[sum/2];
		i=j;
	}
	printf("%d\n",ans);
}

int main()
{
	while(~scanf("%d%d",&M,&N),N+M)
	{
		Input();
		dp();
	}
	return 0;
}

以后做了在更新

个人愚昧观点,欢迎指正和讨论

抱歉!评论已关闭.