这里讲两道题目(类型均是01背包类型的),两道题目均用了回溯法和动态规划两种解决办法,做了以后还是有所启发的。
第一道题目就是著名的01背包问题。
01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为W1,W2……Wn,与之相对应的价值为P1,P2……Pn。求背包能放的最大价值。
回溯法的解法:
//0-1背包问题,假定n为8(总共有8种物品),M=110(总共能放的重量为110),重量是w={1,11,21,23,33,43,45,55},价值p={11,21,31,33,43,53,55,65},且已按单位价值排好序
#define maxsize 9
#include <IOSTREAM>
using namespace std;
int n=8;
int M=110; //总共能放的重量为110
int bestp=0; //最大的价值
int noww=0; //当前容量
double nowp=0; //当前价值,会出现小数的
int x[maxsize]={0}; //用来判断物品是否被选
int bestx[maxsize]={0}; //用来表示最优解下哪些物品被选
int w[maxsize]={0,1,11,21,23,33,43,45,55}; //物品重量,0号元素不用
int p[maxsize]={0,11,21,31,33,43,53,55,65}; //价值,0号元素不用
int Check(int noww,int k,int nowp)
{
int flag=0;
for (int i=k+1;i<=n;i++)
{
if (noww+w[i]<M) //如果可以放就放进去
{
noww=noww+w[i];
nowp=nowp+p[i];
}
else
{
flag=1; //flag=1表示当前有放不进去的
break;
}
}
if (flag==1) //如果不是全部可以放进去
{
nowp=nowp+(p[i]/w[i])*(M-noww); //分割后装满算最大价值
}
return nowp;
}
void Backtrack(int noww,int k,int nowp) //noww为当前的重量,k为层数(第几个物品),nowp为现在的价值
{
if (k>n) //表明所有的物品已经全部选择过了
{
if (nowp>bestp||bestp==0) //1.当前的解比之前的最优解还要好,把当前解给最优解。2.如果是第一次也要赋值
{
bestp=nowp;
for(int i=1;i<=n;i++)
{
bestx[i]=x[i];
}
}
return; //要返回不然会一直递归下去
}
if(noww+w[k]<=M) //如果到这一层为止的数小于M,则进入左子树,否则一定不可能,左子树便不生成
{
x[k]=1;
Backtrack(noww+w[k],k+1,nowp+p[k]);
}
if (Check(noww,k,nowp)>=bestp) //重要的剪枝,如果这一层不加,加上剩余的最大值(考虑分割,虽然不能取,但可以找到一个最大值),如果这个最大值比最优解还要小,那右子树不用考虑
{
x[k]=0;
Backtrack(noww,k+1,nowp);
}
}
int main()
{
cout<<"0-1背包问题,假定n为8(总共有8种物品),M=110(总共能放的重量为110),重量是w={1,11,21,23,33,43,45,55},价值p={11,21,31,33,43,53,55,65},且已按单位价值排好序。"<<endl<<endl<<endl;
Backtrack(0,1,0);
cout<<"最优解是:选重量是";
for (int i=1;i<=n;i++)
{
if (x[i]==1)
{
cout<<w[i]<<",";
}
}
cout<<"总价值为"<<bestp<<"的物品。"<<endl;
return 0;
}
动态规划的解法:
//0-1背包问题
#include <IOSTREAM>
using namespace std;
int n=4;
int C=11;
int V[4+1][11+1];
int p[4+1]={0,3,4,5,7};
int w[4+1]={0,2,3,5,6};
int X[11+1];
int Knapsack()
{
for (int i = 0; i <= C; ++i)
{
X[i] = 0;
}
for (int j = 1; j <= n; ++j)
{
for (int i = C; i >= 1; --i)
{
if (i >= w[j] && X[i - w[j]] + p[j] > X[i])
{
X[i] = X[i - w[j]] + p[j];
}
}
}
return X[C];
}
int main()
{
int bestp=Knapsack();
cout<<"选总价值为"<<bestp<<"的物品。"<<endl;
return 0;
}
第二道题目是去年暑假做的。浙大07年考研复试题最大报销额。
题目是这样的:
现有一笔经费可以报销一定额度的发票。允许报销的发票类型包括买图书(A类)、文具(B类)、差旅(C类),要求每张发票的总额不得超过1000元,每张发票上,单项物品的价值不得超过600元。现请你编写程序,在给出的一堆发票中找出可以报销的、不超过给定额度的最大报销额。
输入:
测试输入包含若干测试用例。每个测试用例的第1行包含两个正数 Q 和 N,其中 Q 是给定的报销额度,N(N<=30)是发票张数。随后是 N 行输入,每行的格式为:
m Type_1:price_1 Type_2:price_2 … Type_m:price_m
其中正整数 m 是这张发票上所开物品的件数,Type_i 和 price_i 是第 i 项物品的种类和价值。物品种类用一个大写英文字母表示。当N为0时,全部输入结束,相应的结果不要输出。
输出:
对每个测试用例输出1行,即可以报销的最大数额,精确到小数点后2位。
样例输入:
200.00 3
2 A:23.50 B:100.00
1 C:650.00
3 A:59.99 A:120.00 X:10.00
1200.00 2
2 B:600.00 A:400.00
1 C:200.50
1200.50 3
2 B:600.00 A:400.00
1 C:200.50
1 A:100.00
100.00 0
样例输出:
123.50
1000.00
1200.50
当时碰到这道题目,第一想法就是用回溯法,于是写下了如下程序:
#include <iostream>
#include <vector>
#include <string.h>
#include <string>
#include <stdlib.h>
using namespace std;
typedef struct Unit
{
double A;
double B;
double C;
double sum;
}Unit;
vector <Unit> ve;
double maxsum, Q;
void dfs(int k, double sum)
{
if (k >= ve.size())
{
if (sum>maxsum)
{
maxsum = sum;
}
}
else
{
dfs(k + 1, sum);
if (sum + ve[k].sum <= Q)
{
dfs(k + 1, sum + ve[k].sum);
}
}
}
int main()
{
int i, N, j, num, flag;
double ctmp;
string temp, subtmp;
Unit utmp;
while (cin >> Q >> N, N != 0)
{
ve.clear();
for (i = 0; i<N; i++)
{
flag = 0;
utmp.A = 0;
utmp.B = 0;
utmp.C = 0;
utmp.sum = 0;
cin >> num;
for (j = 0; j<num; j++)
{
cin >> temp;
if (temp[0] == 'A')
{
subtmp = temp.substr(2);
ctmp = atof(subtmp.data());
if (ctmp <= 600)
{
utmp.A += ctmp;
utmp.sum += ctmp;
}
else
{
flag = 1;
break;
}
}
else if (temp[0] == 'B')
{
subtmp = temp.substr(2);
ctmp = atof(subtmp.data());
if (ctmp <= 600)
{
utmp.B += ctmp;
utmp.sum += ctmp;
}
else
{
flag = 1;
break;
}
}
else if (temp[0] == 'C')
{
subtmp = temp.substr(2);
ctmp = atof(subtmp.data());
if (ctmp <= 600)
{
utmp.C += ctmp;
utmp.sum += ctmp;
}
else
{
flag = 1;
break;
}
}
else
{
flag = 1;
break;
}
}
if (flag == 0 && utmp.sum <= 1000 && utmp.sum <= Q)
{
ve.push_back(utmp);
}
}
maxsum = 0;
dfs(0, 0);
cout.setf(ios::fixed);
cout.precision(2);
cout << maxsum << endl;
}
system("pause");
return 0;
}
然而提交的时候发现超时了,看来回溯法还是太慢了。后来在网上找答案时发现了这道题的动态规划解法,由于是别人的代码,我这边就不贴了。网址是http://blog.csdn.net/xiexingshishu/article/details/8799669。
由于当时年轻(其实是笨),没有发现这其实就是个01背包问题,若回溯法时间太长的话可以用动态规划解来减少时间。