您的位置:首页 > 其它

动态规划基础学习(一)线性

2013-07-27 16:33 417 查看
动态规划一直是个大问题。

动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法(俗称DP)。

动态规划一般可分为线性动规,区域动规,树形动规,背包动规四类。

1, 线性动规:

拦截导弹,合唱队形,挖地雷,建学校,剑客决斗等

2 区域动规:

石子合并, 加分二叉树,统计单词个数,炮兵布阵等

3 树形动规:

贪吃的九头龙,二分查找树,聚会的欢乐,数字三角形等

4 背包问题:

01背包问题,完全背包问题,分组背包问题,二维背包,装箱问题,挤牛奶(同济ACM第1132题)等



以上来自百度百科

一个简单的例子来阐述DP的基本思路和特点

第一弹:数字三角形

描述:给定一个由n行数字组成的数字三角形如下图所示。试设计一个算法,计算出从三角形的顶至底的一条路径,使该路径经过的数字总和最大。

思路:从最下面的一个开始查找最优解,不断更新,输出d(1,1)。

//数字三角形
#include <iostream>
using namespace std;
const int M=100;
int n;
int a[M][M];
int func()
{
  int i,j;
 for(i=n-1;i>=1;i--)//从底部开始查找
  for(j=1;j<=i;j++)
  {
   if(a[i+1][j]>a[i+1][j+1]) 
        a[i][j]+=a[i+1][j];
   else a[i][j]+=a[i+1][j+1];//这两行就是DP的核心:状态转移方程
  } 
  return a[1][1];
}
int main()
{
 int i,j,max;
  cin>>n;
 for(i=1;i<=n;i++)
  for(j=1;j<=i;j++)
   cin>>a[i][j];
   max=func();
  cout<<max<<endl;
  return 0;
}




样例输入输出可见出算法的规律



BUY LOW, BUY LOWER

Time Limit: 1000MSMemory Limit: 30000K
Total Submissions: 8200Accepted: 2852
Description
The advice to "buy low" is half the formula to success in the bovine stock market.To be considered a great investor you must also follow this problems' advice:

"Buy low; buy lower"


Each time you buy a stock, you must purchase it at a lower price than the previous time you bought it. The more times you buy at a lower price than before, the better! Your goal is to see how many times you can continue purchasing at ever lower prices.

You will be given the daily selling prices of a stock (positive 16-bit integers) over a period of time. You can choose to buy stock on any of the days. Each time you choose to buy, the price must be strictly lower than the previous time you bought stock. Write
a program which identifies which days you should buy stock in order to maximize the number of times you buy.

Here is a list of stock prices:

Day   1  2  3  4  5  6  7  8  9 10 11 12

Price 68 69 54 64 68 64 70 67 78 62 98 87


The best investor (by this problem, anyway) can buy at most four times if each purchase is lower then the previous purchase. One four day sequence (there might be others) of acceptable buys is:

Day    2  5  6 10

Price 69 68 64 62


Input
* Line 1: N (1 <= N <= 5000), the number of days for which stock prices are given

* Lines 2..etc: A series of N space-separated integers, ten per line except the final line which might have fewer integers.

Output
Two integers on a single line:

* The length of the longest sequence of decreasing prices

* The number of sequences that have this length (guaranteed to fit in 31 bits)

In counting the number of solutions, two potential solutions are considered the same (and would only count as one solution) if they repeat the same string of decreasing prices, that is, if they "look the same" when the successive prices are compared. Thus,
two different sequence of "buy" days could produce the same string of decreasing prices and be counted as only a single solution.

Sample Input
12
68 69 54 64 68 64 70 67 78 62
98 87

Sample Output
4 2

Source
USACO 2002 February

译 by Twink

“逢低吸纳”是炒股的一条成功秘诀。如果你想成为一个成功的投资者,就要遵守这条秘诀:

"逢低吸纳,越低越买"

这句话的意思是:每次你购买股票时的股价一定要比你上次购买时的股价低.按照这个规则购买股票的次数越多越好,看看你最多能按这个规则买几次。

给定连续的N天中每天的股价。你可以在任何一天购买一次股票,但是购买时的股价一定要比你上次购买时的股价低。写一个程序,求出最多能买几次股票。

以下面这个表为例, 某几天的股价是:

天数 1 2 3 4 5 6 7 8 9 10 11 12

股价 68 69 54 64 68 64 70 67 78 62 98 87

这个例子中, 聪明的投资者(按上面的定义),如果每次买股票时的股价都比上一次买时低,那么他最多能买4次股票。一种买法如下(可能有其他的买法):

天数 2 5 6 10

股价 69 68 64 62

题目的意思比较的简单,给出一列由n个数字组成的数,求出最长递减子序列的长度,并求出共有多少种最长递减子序列。
只有一行,输出两个整数:

能够买进股票的天数

长度达到这个值的股票购买方案数量

在计算解的数量的时候,如果两个解所组成的字符串相同,那么这样的两个解被认为是相同的(只能算做一个解)。因此,两个不同的购买方案可能产生同一个字符串,这样只能计算一次。

第一问是一个比较简单的求最长下降子序列问题
用opt[i]数组记录序列中必须用到i的最长下降子序列中的元素的个数
天数 1 2 3 4 5 6
价格 5 6 4 3 1 2
opt 1 1 2 3 4 4
核心代码就一行

[cpp]
view plaincopyprint?





for(int i=1;i<n;i++)
{
for(int j=0;i<i;j++)
{
if(a[j]>a[i]&&opt[j]+1>opt[i]) opt[i]=opt[j]+1;
}
}

for(int i=1;i<n;i++)
{
    for(int j=0;i<i;j++)
    {
        if(a[j]>a[i]&&opt[j]+1>opt[i]) opt[i]=opt[j]+1;
    }
}


第二问就比较复杂了
建立一个函数F[i]表示前i张中用到第i张股票的方案数

[cpp]
view plaincopyprint?





#include<iostream>
#include<string.h>
//F[i]函数表示前i张中用到第i张股票的方案数
using namespace std;
int main()
{
int N,maximum,obj[5005],price[5005];
int F[5005],max_F;
while(cin>>N&&N)
{
for(int i=0; i<N; i++)
{
F[i]=1;
obj[i]=1;
}
for(int i=0; i<N; i++)
cin>>price[i];
maximum=-1;
for(int i=0; i<N; i++)
{
for(int j=i-1; j>=0; j--)
{
if(price[j]>price[i])
{
if(obj[j]+1>obj[i])
{
obj[i]=obj[j]+1;
F[i]=F[j];
}
else if((obj[j]+1)==obj[i]) //obj数组有没有连续的数字,有的话就要进行运算
F[i]+=F[j];
}
else
{
if(price[j]==price[i])
{
if(obj[i]==1) //price相同并且是最大的此时F要置零
F[i]=0;
break;
}
}
}
if(obj[i]>maximum)
maximum=obj[i];
}
max_F=0;
for(int i=0; i<N; i++)
{
// cout<<F[i]<<endl;
if(obj[i]==maximum)
max_F+=F[i];
}
cout<<maximum<<" "<<max_F<<endl;
}

return 0;
}

#include<iostream>
#include<string.h>
//F[i]函数表示前i张中用到第i张股票的方案数
using namespace std;
int main()
{
    int N,maximum,obj[5005],price[5005];
    int F[5005],max_F;
    while(cin>>N&&N)
    {
        for(int i=0; i<N; i++)
        {
            F[i]=1;
            obj[i]=1;
        }
        for(int i=0; i<N; i++)
            cin>>price[i];
        maximum=-1;
        for(int i=0; i<N; i++)
        {
            for(int j=i-1; j>=0; j--)
            {
                if(price[j]>price[i])
                {
                    if(obj[j]+1>obj[i])
                    {
                        obj[i]=obj[j]+1;
                        F[i]=F[j];
                    }
                    else if((obj[j]+1)==obj[i])     //obj数组有没有连续的数字,有的话就要进行运算
                        F[i]+=F[j];
                }
                else
                {
                    if(price[j]==price[i])
                    {
                        if(obj[i]==1)               //price相同并且是最大的此时F要置零
                            F[i]=0;
                        break;
                    }
                }
            }
            if(obj[i]>maximum)
                maximum=obj[i];
        }
        max_F=0;
        for(int i=0; i<N; i++)
        {
            // cout<<F[i]<<endl;
            if(obj[i]==maximum)
                max_F+=F[i];
        }
        cout<<maximum<<" "<<max_F<<endl;
    }

    return 0;
}

对F[i]的具体解释:
天数: 1 2 3 4 5 6
价格: 5 6 4 3 1 2
opt: 1 1 2 3 4 4
F: 1 1 2 2 2 2
F[1] {5}
F[2] {6}
F[3] {5 4} {6 4 }
F[4] {5 4 3 } {6 4 3 }
F[5] {5 4 3 1} {6 4 3 1 }
F[6] {5 4 3 2 } {6 4 3 2 }
最后找到 opt[i]=x 然后 sum( F[i] ) 即可

题目二 ships



(ships.pas/c/cpp)

来源:《奥赛经典》(提高篇)

【问题描述】

PALMIA国家被一条河流分成南北两岸,南北两岸上各有N个村庄。北岸的每一个村庄有一个唯一的朋友在南岸,且他们的朋友村庄彼此不同。

每一对朋友村庄想要一条船来连接他们,他们向政府提出申请以获得批准。由于河面上常常有雾,政府决定禁止船只航线相交(如果相交,则很可能导致碰船)。

你的任务是编写一个程序,帮助政府官员决定批准哪些船只航线,使得不相交的航线数目最大。

【输入文件】ships.in

输入文件由几组数据组成。每组数据的第一行有2个整数X,Y,中间有一个空格隔开,X代表PALMIA河的长度(10<=X<=6000),Y代表河的宽度(10<=Y<=100)。 第二行包含整数N,表示分别坐落在南北两岸上的村庄的数目(1<=N<=5000)。在接下来的N行中,每一行有两个非负整数C,D,由一个空格隔开,分别表示这一对朋友村庄沿河岸与PALMIA河最西边界的距离(C代表北岸的村庄,D代表南岸的村庄),不存在同岸又同位置的村庄。最后一组数据的下面仅有一行,是两个0,也被一空格隔开。

【输出文件】ships.out

对输入文件的每一组数据,输出文件应在连续的行中表示出最大可能满足上述条件的航线的数目。

【输入样例】

30 4

7

22 4

2 6

10 3

15 12

9 8

17 17

4 2
0 0

【输出样例】

4

PS:这道题目相对来说思考难度较高,从字面意义上看不出问题的本质来,有点无法下手的感觉。





仔细观察上图:红色航线是合法的,那么他们满足什么规律呢?

C1 C2 C3 C4

北岸红线的端点: 4 9 15 17

南岸红线的端点: 2 8 12 17

D1 D2 D3 D4

不难看出无论线的斜率如何,都有这样的规律:

C1<C2<C3<C4 且 D1<D2<D3<D4

如果我们把输入数据排升序,问题变抽象为:

在一个序列(D)里找到最长的序列满足DI<DJ<Dk……且i<j<k

这样的话便是典型的最长非降子序列问题了

对于任意两条航线如果满足Ci<Cj 且Di<Dj 则两条航线不相交。这样的话要想在一个序列里让所有的航线都不相交那比然满足,C1<C2<C3…Cans且D1<D2<D3…<Dans ,也就是将C排序后求出最长的满足这个条件的序列的长度就是解。

这样分析后显然是一个最长非降子序列问题。

复杂度:排序可以用O(nlogn)的算法,求最长非降子序列时间复杂度是O(n^2).

总复杂度为O(n^2)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: