目录
一.思想
动态规划的思想实质是分治思想和解决冗余。 与分治法类似的是,将原问题分解成若干个子问题,先求解子问题,再从这些子问题的解得到原问题的解。与分治法不同的是,经分解的子问题往往不是互相独立的。若用分治法来解,有些共同部分(子问题或子子问题)被重复计算了很多次。如果能够保存已解决的子问题的答案,在需要时再查找,这样就可以避免重复计算、节省时间。动态规划法用一个表来记录所有已解的子问题的答案。
动态规划方法的基本思想是,把求解的问题分成许多阶段或多个子问题,然后按顺序求解各子问题。最后一个子问题就是初始问题的解。
由于动态规划的问题有重叠子问题的特点,为了减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。
二.框架
1.适合动态规划的问题特征
动态规划算法的问题及决策应该具有三个性质:最优化原理、无后向性、子问题重叠性质
1) 最优化原理(或称为最佳原则、最优子结构)
“一个过程的最优决策具有这样的性质:即无论其初始状态和初始决策如何,其今后诸策略对以第一个决策所形成的状态作为初始状态的过程而言,必须构成最优策略”。简言之,一个最优策略的子策略,对于它的初态和终态而言也必是最优的。
这个“最优化原理”如果用数学化一点的语言来描述的话,就是:假设为了解决某一优化问题,需要依次作出n个决策D1,D2,…,Dn,如若这个决策序列是最优的,对于任何一个整数k,1 < k < n,不论前面k个决策是怎样的,以后的最优决策只取决于由前面决策所确定的当前状态,即以后的决策Dk+1,Dk+2,…,Dn也是最优的。
2) 无后向性(无后效性)
下一时刻的状态只与当前状态有关,而和当前状态之前的状态无关,当前的状态是对以往决策的总结。
简单的说,就是当前的状态是此前历史的一个完整总结,此前的历史只能通过当前的状态去影响过程未来的演变,“未来与过去无关”。
具体地说,如果一个问题被划分各个阶段之后,阶段i中的状态只能由阶段i-1中(或多个有限历史阶段)的状态通过状态转移方程得来,与其它状态没有关系,特别是与未发生的状态没有关系。
3) 有重叠子问题
在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格(数组)中,在以后尽可能多地利用这些子问题的解。
三.基本步骤
设计一个标准的动态规划算法:
1) 划分阶段
2) 选择状态
3) 确定决策并写出状态转移方程
但是,实际应用当中的简化步骤:
1) 分析最优解的性质,并刻划其结构特征。
2) 递推地定义最优值。
3) 以自底向上的方式或自顶向下的记忆化方法(备忘录法)计算出最优值.
4) 根据计算最优值时得到的信息,构造问题的最优解。
四.例题分析
1.数塔
题目描述
有形如图所示的一个数塔,从顶部出发,在每一结点可以选择向左走或是向右走,一直走到底层,要求找出一条路径,使路径上的数值和最大,路径上的每一步都只能往左下走或右下走。
问题分析
从下往上分析
以上的决策结果将5阶数塔问题变为4阶子问题,递推出第四层与第五层的和:
21(2+19),28(18+10),19(9+10),21(5+16)
用同样的方法还可以将4阶数塔问题,变为3阶数塔问题。…… 最后得到的1阶数塔问题,就是整个问题的最优解。
代码实现
#include<bits/stdc++.h>
using namespace std;
int a[100][100],dp[100][100];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
cin>>a[i][j];
}
memset(dp,0,sizeof(dp));
for(int j=1;j<=n;j++)
dp[n][j]=a[n][j];
for(int i=n-1;i>=1;i--)
{
for(int j=1;j<=i;j++)
dp[i][j]=a[i][j]+max(dp[i+1][j],dp[i+1][j+1]);
}
cout<<dp[1][1]<<endl;
return 0;
}
2.超级楼梯
问题描述
有 n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法
代码实现
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin>>n;
int a[100];
a[1]=1;
a[2]=2;
a[3]=3;
for(int i=4;i<=40;i++)
a[i]=a[i-1]+a[i-2];
cout<<a[n]<<endl;
return 0;
}
3.找零钱
题目描述
假设有 1 元,3 元,5 元的硬币若干(无限),现在需要凑出 11 元,问如何组合才能使硬币的数量最少?
代码实现
#include<bits/stdc++.h>
using namespace std;
int fun(int x)
{
if(x==1)
return 1;
else if(x==2)
return 2;
else if(x==3)
return 1;
else if(x==4)
return 2;
else if(x==5)
return 1;
else
//关键
return min((fun(x-1)+1),min((fun(x-3)+1),fun(x-5)+1));
}
using namespace std;
int main()
{
int money;
while(cin>>money)
{
cout<<fun(money)<<endl;
}
return 0;
}
4.n个矩阵连乘
①第一种方法(和接下来的一种可以做一下对比,据说这个效率比较低)
题目描述
给定n个矩阵M1,M2,…, Mn, Mi的维数为
pi-1×pi(1≤i≤n), 要求计算链乘积M1M2… Mn
由于矩阵乘法满足结合率,所以可以有许多不同的计算次序,这种计算次序可以用加括号的方式来确定
比如: M1,M2,M3,M4
(M1 ( M2 ( M3 M4) ) ) (M1 (( M2 M3 ) M4 ) )
(M1 M2 )( M3 M4 ) (( M1 ( M2 M3) ) M4 )
(( M1 M2 ) M3) M4 )
问题分析
1. 阶段划分
1)初始状态为一个矩阵相乘的计算量为0;
2)第二阶段,计算两个相邻矩阵相乘的计算量, 共n-1组;
3)第三阶段,计算两个相邻矩阵相乘的结果与第三个矩阵相乘的计算量, 共得到n-2组;
4)最后一个阶段,是n个相邻矩阵相乘的计算量,最后得到1组,也就是问题解。
2. 阶段决策
M1 × M2 × M3 × M4
[5×20] [20×50] [50×1] [1×100]
M1为r1×r2, M2 为r2×r3, M3 为r3×r4, M4为r4×r5
•r1, r2, r3, r4, r5分别对应为5, 20, 50, 1, 100
代码实现
#include<bits/stdc++.h>
using namespace std;
int com[100][100];//记录(局部)最优解的括号位置
int r[100];
int course(int i,int j)
{
if(i==j)//一个矩阵相乘
return 0;
if(i==j-1)//两个矩阵相乘
{
com[i][j]=i;
return r[i]*r[i+1]*r[j+1];
}
//一般情况求解(3个矩阵以上)
com[i][j]=i;
int u=course(i,i)+course(i+1,j)+r[i]*r[i+1]*r[j+1];
for(int k=i+1;k<j;k++)
{
int t=course(i,k)+course(k+1,j)+r[i]*r[k+1]*r[j+1];
if(t<u)
{
u=t;
com[i][j]=k;
}
}
return u;
}
int main()
{
int n;
cout<<"请输入矩阵个数"<<endl;
cin>>n;
cout<<"请输入矩阵的规格"<<endl;
for(int i=1;i<=n+1;i++)
cin>>r[i];
cout<<"最优的解是"<<course(1,n)<<endl;
for(int i=1;i<=n;i++)
{
cout<<endl;
for(int j=1;j<=n;j++)
cout<<com[i][j]<<" ";
}
return 0;
}
②第二种方法
问题分析
采用算法设计1实现的步骤与程序,不难发现,其中有许多重复的子问题存在,如(1--2)、(2--3)和(3--4)子问题都被调用了2次。
如何避免重复解决这些子问题?
解决的办法是用二维数组m[i][j]存储已经计算过的course(i,j)值,当需要再次调用course(i,j)时,直接读取数组m[i][j]的值就可以了。
该数组m[i][j]为全局变量,初始化时值都设定为-1,以识别是否是第一次调用course(i,j)。
算法实现
#include<bits/stdc++.h>
using namespace std;
int r[100];
int com[100][100];
int m[100][100];//存储已经计算过的course(i,j)的值,之后会被初始化为0
int course(int i,int j)
{
int u,t,k;
if(m[i][j]>0)
return m[i][j];
if(i==j)
return 0;
if(i==j-1)
{
com[i][j]=i;
m[i][j]=r[i]*r[i+1]*r[j+1];
return m[i][j];
}
com[i][j]=i;
u=course(i,i)+course(i+1,j)+r[i]*r[i+1]*r[j+1];
for(k=i+1;k<j;k++)
{
t=course(i,k)+course(k+1,j)+r[i]*r[k+1]*r[j+1];
if(t<u)
{
u=t;
com[i][j]=k;
}
}
m[i][j]=u;
return u;
}
int main()
{
int n;
cout<<"请问有几个矩阵相乘?"<<endl;
cin>>n;
cout<<"请输入矩阵的规格"<<endl;
for(int i=1;i<=n+1;i++)
cin>>r[i];
for(int i=1;i<=n;i++)//初始化数组com和m
{
for(int j=1;j<=n;j++)
{
com[i][j]=0;
m[i][j]=0;
}
}
course(1,n);
cout<<"最优解为"<<endl;
cout<<m[1][n]<<endl;
for(int i=1;i<=n;i++)
{
cout<<endl;
for(int j=1;j<=n;j++)
cout<<com[i][j]<<" ";
}
return 0;
}
③第3种真正的dp算法前面两个其实都是递归
#include<bits/stdc++.h>
using namespace std;
int r[100];
int com[100][100];
int m[100][100];
int main()
{
int n;
cout<<"请问有几个矩阵相乘?"<<endl;
cin>>n;
cout<<"请输入矩阵的规格"<<endl;
for(int i=1;i<=n+1;i++)
cin>>r[i];
for(int i=1;i<=n;i++)//初始化数组com
{
for(int j=1;j<=n;j++)
{
com[i][j]=0;
}
}
for(int i=1;i<n;i++)
{
m[i][i]=0;
m[i][i+1]=r[i]*r[i+1]*r[i+2];
com[i][i+1]=i+1;
}
m[n][n]=0;
//dp算法
for(int s=2;s<=n-1;s++)
{
for(int i=1;i<n-s+1;i++)
{
int j=i+s;
m[i][j]=m[i][i]+m[i+1][j]+r[i]*r[i+1]*r[j+1];
com[i][j]=i;
for(int k=i+1;k<j;k++)
{
int t=m[i][k]+m[k+1][j]+r[i]*r[k+1]*r[j+1];
if(t<m[i][j])
{
m[i][j]=t;
com[i][j]=k;
}
}
}
}
cout<<"最优解为"<<endl;
cout<<m[1][n]<<endl;
for(int i=1;i<=n;i++)
{
cout<<endl;
for(int j=1;j<=n;j++)
cout<<com[i][j]<<" ";
}
return 0;
}
5.资源分配问题
题目描述
设有资源a,分配给n个项目,gi(x)为第i个项目分得资源x所得到的利润。求总利润最大的资源分配方案,也就是解下列问题:
max z=g1(x1)+ g2(x2)+……gn(xn)
x1+x2+x3+……xn=a, xi≥0, i=1,2,3,……,n
例如:现有7万元投资到A,B,C 三个项目,利润见表,求问题总利润最大的资源分配方案。
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
A | 0.11 | 0.13 | 0.15 | 0.21 | 0.24 | 0.30 | 0.35 |
B | 0.12 | 0.16 | 0.21 | 0.23 | 0.25 | 0.24 | 0.34 |
C | 0.08 | 0.12 | 0.20 | 0.24 | 0.26 | 0.30 | 0.35 |
算法分析
每个阶段要考虑各种分配方案下的最大利润,设fi(x)为将资源x分配给前i个项目所得的最大利润,分阶段决策过程是:
f1(x) = g1(x) 0≤x ≤n
fi(x) = max{gi(x)+fi-1(n-x)} 0≤x ≤n, i=2,3,…,n
由于每一阶段都考虑了所有的投资组合情况,所以算法策略是满足最佳原理的。
用表格进行分析(已经完成)
算法实现
数据结构设计:
1) 开辟一维数组q来存储原始数据。
2) 另开辟一维数组f存储当前最大收益情况。
3) 开辟记录中间结果的一维数组数组temp,记录正在计算的最大收益。
4) 开辟二维数组a。
5) 数组gain存储第i个工程投资数的最后结果。
#inlcude<bits/stdc++.h>
using namespace std;
main( )
{
int i,j,k,m,n,rest;
int a[100][100],gain[100];
float q[100],f[100],temp[100];
printf("How many items?\n");
scanf("%d",&m);
printf("How much? \n");
scanf("%d",&n);
printf("input gain table:\n");
for(j=0;j<=n;j++)
{
scanf("%0.2f",&q[j]);
f[j]=q[j];
}
for(j=0;j<=n;j++)
a[1][j]=j;
for(k=2;k<=m;k++)
{
for(j=0;j<= n;j++)
{
temp[j]=q[j];
scanf("%f",&q[j]);
a[k][j]=0;
}
for(j=0;j<=n;j++)
for(i=0;i<=j;i++)
if(f[j-i]+q[i]>temp[j] )
{
temp[j]=f[j-i]+q[i];
a[k][j]=i;
}
for(j=0;j<= n;j++)
f[j]=temp[j];
}
rest=n;
for(i=m;i>=1;i--)
{
gain[i]=a[i][rest];
rest=rest-gain[i];
}
for(i=1;i<=m;i++)
printf("%d ",gain[i]);
printf("%0.2f\n",f[n]);
}
6.切木棍(这个是老师布置的作业!!!)
--------------------------------------------------------------------------大雾-------------------------------------------------------------------------------------
题目描述
有一根长度为L(L<1000) 的棍子,还有n(n<50)个切割点的位置。要在切割点处把棍子切成n+1 部分,使得总切割费用最小。每次切割的费用等于被切割的木棍长度。例如L=10,切割点为2,4,7。如果按照2,4,7的顺序,费用为10+8+6=24,如果按照4,2,7的顺序,费用为10+4+6=20
算法实现
#include<bits/stdc++.h>
using namespace std;
int point[55],pp[55][55];
int dp(int i,int j)
{
if(i==j-1)
return 0;
if(pp[i][j]!=-1)
return pp[i][j];
int ans=0x3f3f3f3f;
//dp算法过程
for(int k=i+1;k<j;k++)
ans=min(ans,dp(i,k)+dp(k,j)+point[j]-point[i]);
return ans;
}
int main()
{
int n,len;
cout<<"请输入棍子长度"<<endl;
cin>>len;
cout<<"请输入要切割的点数"<<endl;
cin>>n;
cout<<"请输入切割点的位置"<<endl;
for(int i=1;i<=n;i++)
cin>>point[i];
point[0]=0;
point[n+1]=len;
memset(pp,-1,sizeof(pp));
for(int i=0;i<=n+1;i++)
pp[i][i+1]=0;
cout<<"最优解为:"<<dp(0,n+1)<<endl;
return 0;
}
7.合并石子(这个也是作业)
题目描述
在一个圆形操场的四周摆放着n堆石子,现要将石子有次序地合并成一堆。规定每次只能选相邻2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。
输入:n表示n堆石子,下一行n个数,表示每堆石子的个数。
输出:最小得分,最大得分
样例
4
4 5 9 4
43
54
问题分析
由题意可得这些石子是摆放成一个环,环形不好写状态转移方程,那我们就可以把它拆成一条长度为2*n-1
的链,采用区间动规的办法,合并的石子数用前缀和优化一下就好了
状态转移方程:
用f[i][j]
表示从第i堆石子到第j堆石子的最大/最小得分,用k(i<=k<=j-1)将区间分成[i,k]和[k+1,j]两个区间
初值:f[i][i]=0
fmax[i][j]=max{fmax[i][j],fmax[i][k]+f[k+1][j]+s[j]-s[i-1]}
fmin[i][j]=min{fmin[i][j],fmin[i][k]+f[k+1][j]+s[j]-s[i-1]}
#include<iostream>
#include<cstring>
using namespace std;
const int INF=100000000;
int fmax[100][100],fmin[100][100],maxa=-INF,mina=INF,s[100],n;
void dp()
{
//初始化
for(int i=1;i<=2*n-1;i++)
{
for(int j=1;j<=2*n-1;j++)
{
fmax[i][j]=-INF;
fmin[i][j]=INF;
}
}
for(int i=1;i<=2*n-1;i++)
{
fmax[i][i]=0;
fmin[i][i]=0;
}
for(int i=2*n-1;i>=1;i--)
{
for(int j=i+1;j<=2*n-1;j++)
{
for(int k=i;k<j;k++)
{
fmax[i][j]=max(fmax[i][j],fmax[i][k]+fmax[k+1][j]+s[j]-s[i-1]);
fmin[i][j]=min(fmin[i][j],fmin[i][k]+fmin[k+1][j]+s[j]-s[i-1]);
}
}
}
}
void ans()
{
for(int i=1;i<=n;i++)
{
maxa=max(maxa,fmax[i][i+n-1]);
mina=min(mina,fmin[i][i+n-1]);
}
}
int main()
{
cin>>n;
int t;
for(int i=1;i<=n;i++)
{
cin>>t;
s[i]=s[i+n]=t;
}
//前缀和优化
for(int i=1;i<=2*n-1;i++)
s[i]+=s[i-1];
dp();
ans();
cout<<mina<<endl<<maxa;
return 0;
}
8.最长公共字符子序列
题目描述
若给定序列X={x1,x2,…,xm},则另一序列Z={z1,z2,…,zk},是X的子序列是指存在一个严格递增下标序列{i1,i2,…,ik}使得对于所有j=1,2,…,k有:zj=xij。例如,序列Z={B,C,D,B}是序列X={A,B,C,B,D,A,B}的子序列,相应的递增下标序列为{2,3,5,7}。
给定2个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。
给定2个序列X={x1,x2,…,xm}和Y={y1,y2,…,yn},找出X和Y的最长公共子序列。
问题分析
①设 A=“a0, a1, …, am-1”, B=“b0, b1, …, bn-1”,
Z=“z0, z1, …, zk-1”为它们的最长公共子序列。有以下结论:
1)如果am-1=bn-1,则zk-1=am-1=bn-1,且" z0,z1,…,zk-2 "是" a0,a1,…,am-2 "和" b0,b1,…,bn-2 "的一个最长公共子序列;
2)如果am-1≠bn-1,则若zk-1≠am-1,蕴涵" z0,z1,…,zk-1 "是"a0,a1,…,am-2"和"b0,b1,…,bn-1"的一个最长公共子序列;
3)如果am-1≠bn-1,则若zk-1≠bn-1,蕴涵" z0,z1,…,zk-1 "是" a0,a1,…,am-1 "和" b0,b1,…,bn-2 "的一个最长公共子序列。
②定义c[i][j]为序列a0,a1,…,ai-1”和“b0,b1,…,bj-1”的最长公共子序列的长度,计算c[i][j]可递归地表述如下:
1)c[i][j]=0 如果i=0或j=0;
2)c[i][j]=c[i-1][j-1]+1 如果i,j>0,且a[i-1]=b[j-1];
3)c[i][j]=max(c[i][j-1],c[i-1][j]) 如果i,j>0,且a[i-1]≠b[j-1]。
法1:递归式做法,复杂度O(mn)且利用了栈空间,时间和空间效率很低
#include<bits/stdc++.h>
using namespace std;
char a[100],b[100],z[100];
int c[100][100];
int lc_len(int i,int j) //计算最优值
{ int t1,t2;
if(i == 0 || j == 0)
c[i][j]=0;
else if (a[i-1]==b[j-1])
c[i][j]=lc_len(i-1,j-1)+1;
else
{ t1 = lc_len(i,j-1);
t2 = lc_len(i-1,j);
if(t1>t2) c[i][j] = t1;
else c[i][j] = t2;
}
return c[i][j];
}
void build_lc(int k,int i,int j)
{
if(i == 0 || j == 0)
return ;
if(c[i][j] == c[i-1][j])
build_lc(k,i-1,j);
else if(c[i][j] == c[i][j-1])
build_lc(k,i,j-1);
else
{
z[k-1] = a[i-1];
build_lc(k-1,i-1,j-1);
}
}
int main()
{
int m,n,k;
cout<<"input 2 string"<<endl;
cin>>a>>b;
m = strlen(a);
n = strlen(b);
k = lc_len(n,m);
//cout<<k<<endl;
build_lc(k,n,m);
cout<<z<<endl;
return 0;
}
法2:非递归做法
int lc_len()
{
int i,j;
int m = strlen(a);
int n = strlen(b);
for(int i = 0;i <= m;i++)
c[i][0] = 0;
for(int j = 0;j <= n;j++)
c[0][j] = 0;
for(int i = 1;i <= m;i++)
{
for(int j = 1;j <= n;j++)
if(a[i-1] == b[j-1])
c[i][j] = c[i-1][j-1]+1;
else if(c[i-1][j] >= c[i][j-1])
c[i][j] = c[i-1][j];
else
c[i][j] = c[i][j-1];
}
return c[m][n];
}
void build_lc()
{
int i,j,k;
i = strlen(a);
j = strlen(b);
k = lc_len();
while(k > 0)
{
if(c[i][j] == c[i-1][j])
i--;
else if(c[i][j] == c[i][j-1])
j--;
else
{
k--;
z[k] = a[i-1];
j--;
}
}
}
int main()
{
cout<<"input 2 string"<<endl;
cin>>a>>b;
build_lc();
cout<<z<<endl;
return 0;
}
9.最大字段和
问题描述
求一个序列的最大子段和即最大连续子序列之和。例如序列[4, -3, 5, -2, -1, 2, 6, -2]的最大子段和为11=[4+(-3)+5+(-2)+(-1)+(2)+(6)]
问题分析
可以采用分治法和动态规划解决,前面一章已经用分治法O(nlogn)解决了,这里用动态规划O(n)解决一下
b[j] = max{b[j-1]+a[j],a[j]}
#include<bits/stdc++.h>
using namespace std;
int fun(const int a[],int n)
{
int tempsum = 0;
int maxsum = 0;
for(int j = 0;j < n;j++)//子问题后边界
{
tempsum = (tempsum + a[j]) > a[j]?tempsum + a[j]:a[j];
//更新最大值
maxsum = max(tempsum,maxsum);
}
return maxsum;
}
int main()
{
int a[100];
int n;
cout<<"input the length of the array"<<endl;
cin>>n;
for(int i = 0;i < n;i++)
cin>>a[i];
cout<<"the maxsum is "<<fun(a,n)<<endl;
return 0;
}
10.最长不降子序列
问题描述
设有由n个不相同的整数组成的数列,记为:
a(1)、a(2)、……、a(n)且 a(i)≠a(j) (i≠j)
若存在i1<i2<i3< … <ik 且有a(i1)<a(i2)< … <a(ik),则称为长度为k的不下降序列。请求出一个数列的最长不下降序列。
例如:数列3,18,7,14,10,12,23,41,16,24。则3,18,23,24是长度为4的不下降序列。同时,3,7,10,12,16,24和3,7,10,12,23,41都是长度为6的最长不下降序列。
问题分析
1) 对a(n)来说,由于它是最后一个数,所以当从a(n)开始查找时,只存在长度为1的不下降序列;
2) 若从a(n-1)开始查找,则存在下面的两种可能性:
(1)若a(n-1)<a(n)则存在长度为2的不下降序列a(n-1),a(n)。
(2)若a(n-1)>a(n)则存在长度为1的不下降序列a(n-1)或a(n)。
3) 一般若从a(i)开始,此时最长不下降序列应该按下列方法求出:在a(i+1),a(i+2),…,a(n)中,找出一个比a(i)大的且最长的不下降序列,作为它的后继。
#include<bits/stdc++.h>
using namespace std;
int a[100],b[100],c[100];
int main( )
{
int n,i,j,max,p;
cin>>n;
for(i = 1;i <= n;i++)
{
cin>>a[i];
b[i]=1;
c[i]=0;
}
for(i = n-1; i >= 1;i--)
{
max = 0;
p = 0;
for(j = i+1; j <= n;j++)
if (a[i] < a[j] && b[j] > max)
{
max = b[j];
p = j;
}
if(p != 0)
{
b[i] = b[p]+1;
c[i] = p;
}
}
max = 0;
p = 0;
for(i = 1;i < n;i++)
if(b[i] > max)
{
max = b[i];
p = i;
}
cout<<"maxlong = "<<max<<endl;
cout<<"result is"<<endl;
while (p!=0 )
{
cout<<a[p]<<" ";
p=c[p];
}
return 0;
}
11.0-1背包问题(这个不用具体考虑程序实现)
问题描述
给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?(0-1背包问题是一个特殊的整数规划问题)
问题分析
对物品按照顺序编号,令子问题(i,w)表示求给定物品集合{1, 2, …, i},背包载重量为w的最大价值问题
这里有个定理:假设子问题(i, w)的最优装载中含有物品i,则其中子问题(i-1, w-wi)的装载方案也一定是最优的。
证明:用反证法。假设子问题(i-1, w-wi)的装载方案p不是最优的,则有一个更优的装载方案q,将q替换p然后再加物品i将会比原来的最优装载的价值更大,与假设矛盾。
令V[i, w]表示物品1到物品i的物品装入载重量为w的背包中所能获得的最大价值,即子问题(i, w)的最大价值。现考虑两种情况:
(1)物品i能够装进背包,则子问题(i, w)的最优解取决于物品i是否放进去。
①如果物品i不放进去,则有V[i, w] = V[i-1, w] ;
②若将物品i放进去,则有V[i, w]= V[i-1, w-wi]+vi。
有:V[i, w] = max{V[i-1, w], V[i-1, w-wi]+vi}
(2)物品i无法放入背包,则子问题(i-1, w)的最优解一定是子问题(i, w)的最优解,即V[i, w] = V[i-1, w] 。
这个问题重点掌握递推式,然而我在这里无法打开(T_T)
看一个实际例子吧 w=50
物品号i | wi | vi |
1 | 10 | 60 |
2 | 20 | 100 |
3 | 30 | 120 |
4 | 20 | 110 |
V[I,w] | 0 | 10 | 20 | 30 | 40 | 50 |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 60 | 60 | 60 | 60 | 60 |
2 | 0 | 60 | 100 | 160 | 160 | 160 |
3 | 0 | 60 | 100 | 160 | 180 | 220 |
4 | 0 | 60 | 110 | 170 | 210 | 230 |