算法学习笔记4-动态规划-背包问题

动态规划(Dynamic Programming)

其实动态规划特别抽象,特别难讲。这里也可以参考一篇博文,用讲故事的方式讲了出来。
通过金矿模型介绍动态规划
我的理解就是“用更多的问题来回答问题”。
动态规划是一种求解最优化问题的方法,因为它没有一个确定的数学表达式,或者是明确的解题步骤,说起来会比较抽象,我们只能在实际题目中体会。首先说几个概念吧:
这里先给个栗子:
给定一个整数序列,求这个序列的最长上升子序列,子序列可以是不连续的。比如对于序列(1,7,2,8,3,4),它的子序列是(1,2,3,4)。
1. 阶段
动态规划含有一种递推的思想,如果一个问题可以分成不同的“阶段”,上一个阶段的决策直接影响下一个阶段,我们就可以考虑使用动态规划,依次求出每一个阶段的最优解,直到解决整个问题,也就是拆分问题。
比如上面的例子,我们可以把问题拆解为求以k结尾的序列的最长子序列,也就是分别求序列{[1],[1,7],[1,7,2],[1,7,2,8],[1,7,2,8,3],[1,7,2,8,3,4]}的最长上升子序列。
2. 状态
每一个阶段的客观条件我们称为“状态”。一个阶段可能有一个或多个状态,我们可以用一个状态变量来表示,比如上面每一个阶段可以表示为{[1],[1,7],[1,7,2],[1,7,2,8],[1,7,2,8,3],[1,7,2,8,3,4]}。
这里我们可以看到,其实阶段和状态有时候是不区分的。
3. 决策(状态转移方程)
从一个阶段演变到下一个阶段,叫做“决策”,也就是说不同的决策可能会演变到不同的阶段。
而状态跟状态之间的关系式,叫“状态转移方程”,其实它影响了我们的决策,比如上面的例子,在选择数字1之后,下一步是选择7还是选择2,就需要决策,而如果我们的状态转移方程是F_i=min(A_i,A_i-1),那么我们就会选择较小的2。
这样,每个阶段作出决策,我们就能得到最终问题的解。
上面例子的具体代码实现如下:

#include
#include
#include
using namespace std;
int main()
{
    int n;
    vector<int>v;
    vector<int>::iterator it;
    cin>>n;
    int *a=new int[n];
    int *dp=new int[n]{1};
    for(int i=0;icout<" ";
    cout<for(int i=0;icin>>a[i];
    for(int i=1;ifor(int j=0;jif(a[j]1);
        }
    }
    cout<1]<for(int i=0;icout<" ";
    cout<delete []a;
    delete []dp;
    return 0;
}

下面给出几个实际问题:
1.背包问题

题目描述:
辰辰是个很有潜能、天资聪颖的孩子,他的梦想是称为世界上最伟大的医师。 为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。 医师把他带到个到处都是草药的山洞里对他说: “孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。 我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。” 如果你是辰辰,你能完成这个任务吗?
输入描述:
输入的第一行有两个整数T(1 <= T <= 1000)和M(1 <= M <= 100),T代表总共能够用来采药的时间,M代表山洞里的草药的数目。
接下来的M行每行包括两个在1到100之间(包括1和100)的的整数,分别表示采摘某株草药的时间和这株草药的价值。

#coding = utf-8
#backpack problem/dynamic problem
while True:
    try:
        time,m=raw_input().split()
        time=int(time)
        m=int(m)
        t=[]#time
        v=[]#value
        for i in xrange(m):
            t1,t2=raw_input().split()
            t1=int(t1)
            t2=int(t2)
            t.append(t1)
            v.append(t2)
        dp=[0 for i in range(time+1)]
        for i in xrange(m):
            for j in xrange(time,t[i]-1,-1):
                dp[j]=max(dp[j],dp[j-t[i]]+v[i])
        print dp[time]
    except:
        break

2.合唱团

题目描述:
有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?
输入描述:
每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。

#coding=utf-8
n=input()
a=list(raw_input().split())
a=[int(i) for i in a]
ik,d=raw_input().split()
ik=int(ik)
d=int(d)
#动态规划,用两个数组,保留一正一负两个结果
mx=[[0 for i in range(n)] for i in range(ik)]
mn=[[0 for i in range(n)] for i in range(ik)]
m=0
for i in range(n):
    mx[0][i]=mn[0][i]=a[i]
    for k in range(1,ik):
        for j in range(i-1,-1,-1):
            if i-j>d:
                break
            mx[k][i]=max(mx[k][i],max(mx[k-1][j]*a[i],mn[k-1][j]*a[i]))
            mn[k][i]=min(mn[k][i],min(mx[k-1][j]*a[i],mn[k-1][j]*a[i]))
    m=max(m,mx[ik-1][i])
print m

你可能感兴趣的:(算法)