动态规划----揹包问题

1、开心的金明

       问题描述
    金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N 元钱就行”。今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的N 元。于是,他把每件物品规定了一个重要度,分为5 等:用整数1~5 表示,第5 等最重要。他还从因特网上查到了每件物品的价格(都是整数元)。他希望在不超过N 元(可以等于N 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。设第j 件物品的价格为v[j],重要度为w[j],共选中了k 件物品,编号依次为j1…jk,则所求的总和为:v[j1]*w[j1]+..+v[jk]*w[jk]请你帮助金明设计一个满足要求的购物单.

    输入文件

    输入的第1 行,为两个正整数,用一个空格隔开: N  m
(其中N(<30000)表示总钱数,m(<25)为希望购买物品的个数。)
从第2 行到第m+1 行,第j 行给出了编号为j-1的物品的基本数据,每行有2 个非负整数 v p
(其中v 表示该物品的价格(v≤10000),p 表示该物品的重要度(1~5))
   输出文件
   输出只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<100000000)


   输入样例
1000 5
800 2
400 5
300 5
400 3
200 2

输出样例
3900

问题分析:

       很容易分析,此题就是典型的01揹包,就是,给定一定的n,在n个物品中,选出其使用n情况下的最大价值(value)。

#include<iostream>

using namespace std;
int main()
{
	int m,n,i,v;
	int w[25],c[25],f[30001];
	cin>>m>>n;
	for(i=1;i<=n;i++)
	{
		cin>>w[i]>>c[i];
		c[i]*=w[i];
	}
	for(i=1;i<=n;i++)
	{
		for(v=m;v>=w[i];v--)//一维空间的思维。其实也就是在以前的最优的情况下,进行的一个推导。 
		{
			if(f[v-w[i]]+c[i]>f[v])
			{
				f[v]=f[v-w[i]]+c[i];
			}
		}
	}
	cout<<f[m]<<endl;
	return 0;
} 



2、金明的预算方案

    问题描述
    金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是,
妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。
今天一早,金明就开始做预算了,他把想买的物品分为两类:
主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件
 附件
 
电脑
 打印机,扫描仪
 
书柜
 图书
 
书桌
 台灯,文具
 
工作椅
 无
    如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有0个、1个或2个附件。
附件不再有从属于自己的附件。金明想买的东西很多,肯定会超过妈妈限定的N元。于是,他把每件物品规定了一个重要度
,分为5等:
用整数1~5表示,第5等最重要。他还从因特网上查到了每件物品的价格(都是10元的整数倍)。
他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。//一步步的往上。
    设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,……,jk,则所求的总和为:
v[j1]*w[j1]+v[j2]*w[j2]+ …+v[jk]*w[jk]。(其中*为乘号)
请你帮助金明设计一个满足要求的购物单。


输入文件
    输入文件budget.in 的第1行,为两个正整数,用一个空格隔开:
N  m (其中N(<32000)表示总钱数,m(<60)为希望购买物品的个数。)
    从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有3个非负整数: v  p  q
(其中v表示该物品的价格(v<10000),p表示该物品的重要度(1~5),q表示该物品是主件还是附件。如果q=0,表示该物品为主件,如果q>0,表示该物品为附件,q是所属主件的编号)


输出文件

    输出文件budget.out只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<200000)。

输入样例
1000 5

800 2 0

400 5 1

300 5 1

400 3 0

500 2 0

输出样例
2200

问题分析:有了第一道例题的分析,我们分析,我们可以转化到0/1揹包问题上。因为,对于每种物品还是选择,选与不选。阶段还是在物品的个数上。只是我第一次的时候,我在处理附件和主键的时候出现了问题,我以为把主件和附件捆绑,然后,对于每一个主键它至多有四种情况,主,主、附一,主、附二,主、附一、附二,然后把这四个看成四种不同的物品。。。。。忽略了主的重复性。正确的做法是对于每一维,每一个容量,进行比较这四个大小。(维度就是主件的大小)。

#include<cstdio>  
#include<iostream>  
using namespace std;  
const int mm=33333;  
int f[99][mm],v[99],p[99],q[99];  
int i,n,m;  
void TreeDP(int k,int c)  
{  
    if(c)for(int i=1,j;i<=n;++i)  
        if(q[i]==k)  
        {  
            for(j=0;j<=c-v[i];++j)f[i][j]=f[k][j]+v[i]*p[i];  
            TreeDP(i,c-v[i]);  
            for(j=v[i];j<=c;++j)  
                f[k][j]=max(f[k][j],f[i][j-v[i]]);  
        }  
}  
int main()  
{  
    while(~scanf("%d%d",&m,&n))  
    {  
        for(i=1;i<=n;++i)  
            scanf("%d%d%d",&v[i],&p[i],&q[i]);  
        for(i=0;i<=m;++i)f[0][i]=0;  
        TreeDP(0,m);  
        printf("%d\n",f[0][m]);  
    }  
    return 0;  
}  

总的来说,这题挺美的;

3、

Money Systems

(money.pas/c/cpp)

来源:USACO 2.3

问题描述
    母牛们不但创建了他们自己的政府而且选择了建立了自己的货币系统。[In their own rebellious way],,他们对货币的数值感到好奇。
传统地,一个货币系统是由1,5,10,20 或 25,50, 和 100的单位面值组成的。
母牛想知道有多少种不同的方法来用货币系统中的货币来构造一个确定的数值。
举例来说, 使用一个货币系统 {1,2,5,10,…}产生 18单位面值的一些可能的方法是:18×1, 9×2, 8×2+2×1, 3×5+2+1,等等其它。
写一个程序来计算有多少种方法用给定的货币系统来构造一定数量的面值。保证总数将会适合long long (C/C++) 和 Int64 (Free Pascal)。
输入文件
货币系统中货币的种类数目是 V (1<= V<=25)。要构造的数量钱是 N (1<= N<=10,000)。
第 1 行: 二整数, V 和 N
第 2 行: 可用的货币 V 个整数。
 
输出文件

单独的一行包含那个可能的构造的方案数。

输入样例

3 10
1 2 5

输出样例

10

思路分析:

       思路很显然,转化到0/1揹包问题上。只不过,它的边界条件,不再是f[0]=0;而是,f[0]=1;——-每到达1,也就是说,有一条路径。

#include<iostream>

int dp[10001]={0};
int a[26];
using namespace std;
int main()
{
	int v,n;
	int i,j,h;
	scanf("%d%d",&v,&n);
	i=1;
	while(i<=v)
	{
		scanf("%d",&a[i]);
		i++;
	}
	dp[0]=1; 
	for(i=1;i<=v;i++)//经验之法。 
	{
		for(j=a[i];j<=n;j++)
		{
			dp[j]=dp[j]+dp[j-a[i]];   
		}
		
	}	
	printf("%d\n",dp[n]);	
	return 0;
}

4、新年趣事之打牌 

问题描述

    过年的时候,大人们最喜欢的活动,就是打牌了。xiaomengxian不会打牌,只好坐在一边看着。
  这天,正当一羣人打牌打得起劲的时候,突然有人喊道:“这副牌少了几张!”众人一数,果然是少了。
于是这副牌的主人得意地说:“这是一幅特制的牌,我知道整副牌每一张的重量。只要我们称一下剩下的牌的总重量,
就能知道少了哪些牌了。”大家都觉得这个办法不错,于是称出剩下的牌的总重量,开始计算少了哪些牌。
由于数据量比较大,过了不久,大家都算得头晕了。
  这时,xiaomengxian大声说:“你们看我的吧!”于是他拿出笔记本电脑,编出了一个程序,
很快就把缺少的牌找了出来。
  如果是你遇到了这样的情况呢?你能办到同样的事情吗?
输入文件
    第一行一个整数TotalW,表示剩下的牌的总重量。
  第二行一个整数N(1<N<=100),表示这副牌有多少张。
  接下来N行,每行一个整数Wi(1<=Wi<=1000),表示每一张牌的重量。

输出文件
   如果无解,则输出“0”;如果有多解,则输出“-1”;否则,按照升序输出丢失的牌的编号,
相邻两个数之间用一个空格隔开。

输入样例
270
4
100
110
170
200
输出样例
2 4

#include<iostream>
#include<stack>
using namespace std;
int a[101]={0};
int b[101];
int dp[101][10001]={0};
int main()
{
	stack<int> st;
	int total;
	int n;
	int i,j;
	scanf("%d%d",&total,&n);
	i=1;
	while(i<=n)
	{
		scanf("%d",&a[i]);
		i++;
	}
	for(j=1;j<=n;j++)
	{
		dp[j][0]=1;
	}
	for(j=total;j>=a[1];j--)
	{
		dp[1][j]=dp[1][j]+dp[1][j-a[1]];
	}//对了。 
	for(i=2;i<=n;i++)
	{
		for(j=total;j>=0;j--)
		{
			if(j>=a[i])
			{
				dp[i][j]=dp[i-1][j]+dp[i-1][j-a[i]];//可行。 
			}
			else
			{
				dp[i][j]=dp[i-1][j];
			}
			
		}
	}
	j=0;
	if(dp[n][total]==0)
	{
		printf("%d\n",0);
	}
	else
	{
		if(dp[n][total]>=2)
		{
			printf("%d\n",-1);
		}
		else
		{
			for(i=n-1;i>=1;i--)
			{
				if(dp[i][total]==1)
				{
					st.push(i+1);
				}
				else
				{
					total=total-a[i+1];
				}
			}
			if(total==0)
			{
				st.push(1);
			}
			while(!st.empty())
			{
				printf("%d ",st.top());
				st.pop();
			}
			printf("\n");
		}
	} 
	return 0;
}

这道题目很是经典,一开始,我认为与前面的那道货币一样,但是,仔细分析,它要的某个牌由哪些组合而来,对于每张牌是只能弄一次的,所以这显然从后面往前遍历。至于路径吧,我采用的是二维去显示路径。

点赞