动态规划

本文深入探讨了动态规划的基本思想和应用,通过一系列经典题目,如直线交点数、子序列问题、最大子序列和等,详细解析了动态规划算法的设计步骤和实现技巧,旨在帮助读者掌握动态规划的核心概念和解决复杂问题的能力。
摘要由CSDN通过智能技术生成

动态规划

整理自杭电刘春英老师PPT

基本思想

如果各个子问题不是独立的,不同的子问题的个数只是多项式量级,如果我们能够保存已经解决的子问题的答案,而在需要的时候再找出已求得的答案,这样就可以避免大量的重复计算。由此而来的基本思路是,用一个表记录所有已解决的子问题的答案,不管该问题以后是否被用到,只要它被计算过,就将其结果填入表中。

动态规划基本步骤:

动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值(最大值或最小值)的那个解。设计一个动态规划算法,通常可以按以下几个步骤进行:

  • 找出最优解的性质,并刻画其结构特征。
  • 递归地定义最优值。
  • 以自底向上的方式计算出最优值。
  • 根据计算最优值时得到的信息,构造一个最优解。

动态规划问题的特征

动态规划算法的有效性依赖于问题本身所具有的两个重要性质:

  • 最优子结构:当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。
  • 重叠子问题:在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格中,在以后尽可能多地利用这些子问题的解。

例题

  1. (HDU 1466) 平面上有n条直线,且无三线共点,问这些直线能有多少种不同交点数。

思路: m条直线的交点方案数 =(m-r)条平行线与r条直线交叉的交点数 + r条直线本身的交点方案 =(m-r)*r+r条之间本身的交点方案数(0<=r<m)

#include <iostream>
using namespace std;

long long list[21][191]={0};

int main(){
    int i,j,r;
    for(i=0; i<21; i++){
        list[i][0] = 1;
        for(r=0;r<=i;r++){
            for(j=0;j<191;j++){
                if(list[r][j]==1) // r中满足的情况
                    list[i][(i-r)*r+j] = 1; // j代表的是r中满足的交点数
            }
        }
    }
    int n;
    while(cin >>n){
        for (i=0;i<=(n-1)*n/2;i++){
            if(list[n][i]==1){
                if(i!=0)
                    cout << " ";
                cout  << i;
            }
        }
        cout << endl;
    }
}
  1. (HDU 1160) FatMouse believes that the fatter a mouse is, the faster it runs. To disprove this, you want to take the data on a collection of mice and put as large a subset of this data as possible into a sequence so that the weights are increasing, but the speeds are decreasing.

关键是找到动态转移方程;

#include <iostream>
#include <algorithm>
#define Maxn 10010

using namespace std;
struct node{
    int id;
    int weight;
    int speed;
    int pre;
    int  len;
}mouse[Maxn];

bool cmp(const node &m,const node &n){
    if (m.weight != n.weight)
        return m.weight < n.weight;
    return m.speed < n.speed;
}

void print_result(int num){
    if(mouse[num].len==1)
        cout << mouse[num].id << endl;
    else{
        print_result(mouse[num].pre);
        cout << mouse[num].id << endl;
    }
}

int main(){
    int cnt = 0;
    while(cin >> mouse[cnt].weight >> mouse[cnt].speed){
        mouse[cnt].id = cnt + 1;
        mouse[cnt].len = 1;
        cnt ++;
    }
    sort(mouse,mouse + cnt,cmp);
    int i,j;
    for(i=0;i<cnt;i++)
        for(j=0;j<i;j++){
            if(mouse[i].weight>mouse[j].weight && mouse[i].speed<mouse[j].speed){
                if(mouse[i].len<mouse[j].len+1){
                    mouse[i].len = mouse[j].len+1;
                    mouse[i].pre = j;
                }
            }
        }
    
    int num=0;
    for (i=0;i<cnt;i++){
        if(mouse[num].len<mouse[i].len)
            num = i;
    }
    cout << mouse[num].len << endl;
    print_result(num);
    return 0;

}
  1. (HDU 1159)
    A subsequence of a given sequence is the given sequence with some elements (possible none) left out. Given a sequence X = <x1, x2, ..., xm> another sequence Z = <z1, z2, ..., zk> is a subsequence of X if there exists a strictly increasing sequence <i1, i2, ..., ik> of indices of X such that for all j = 1,2,...,k, xij = zj. For example, Z = <a, b, f, c> is a subsequence of X = <a, b, c, f, b, c> with index sequence <1, 2, 4, 6>. Given two sequences X and Y the problem is to find the length of the maximum-length common subsequence of X and Y.
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn=1005;
long long list[maxn][maxn]={0};
int main(){
    char s1[maxn],s2[maxn];
    while(cin >>s1 >> s2){
        int l1 = strlen(s1);
        int l2 = strlen(s2);
        int i,j;
        for(i=1;i<=l1;i++)
            for(j=1;j<=l2;j++){
                if(s1[i-1]==s2[j-1])
                    list[i][j] = list[i-1][j-1] + 1;
                else
                    list[i][j] = max(list[i-1][j],list[i][j-1]);
            }
        cout << list[l1][l2] << endl;
    }
}
  1. (HDU 1003) Given a sequence a[1],a[2],a[3]......a[n], your job is to calculate the max sum of a sub-sequence. For example, given (6,-1,5,4,-7), the max sum in this sequence is 6 + (-1) + 5 + 4 = 14.
#include <iostream>
using namespace std;

#define maxn 100000
long long list[maxn+1]={0};
long long input[maxn]={0};
int main(){
    int t,n,i,j;
    cin >> t;
    int case_i=0;
    while(t--){
        case_i += 1;
        cin >> n;
        for(i=0;i<n;i++){
            cin >> input[i];
            list[i] = input[i];
        }  
        for(i=1;i<n;i++){
            if(list[i]<list[i-1]+list[i])
                list[i] = list[i-1] + list[i];
        }
        int maxsum = list[0];
        int maxindx = 0;
        for(i=0;i<n;i++)
            if(maxsum<list[i]){
                maxsum = list[i];
                maxindx = i;
            }
        int minindx = maxindx;
        for(i=maxindx;i>=0;i--)
            if(list[i]==input[i]){
                minindx = i;
                break;
            }
                
        cout << "Case " << case_i << ":" << endl;
        cout << maxsum << " " << minindx+1 << " " << maxindx+1 << endl;
        if (t>0)
            cout << endl;
    }
}

1.(HDU 1087) The game can be played by two or more than two players. It consists of a chessboard(棋盘)and some chessmen(棋子), and all chessmen are marked by a positive integer or “start” or “end”. The player starts from start-point and must jumps into end-point finally. In the course of jumping, the player will visit the chessmen in the path, but everyone must jumps from one chessman to another absolutely bigger (you can assume start-point is a minimum and end-point is a maximum.). And all players cannot go backwards. One jumping can go from a chessman to next, also can go across many chessmen, and even you can straightly get to end-point from start-point. Of course you get zero point in this situation. A player is a winner if and only if he can get a bigger score according to his jumping solution. Note that your score comes from the sum of value on the chessmen in you jumping path.Your task is to output the maximum value according to the given chessmen list.

#include <iostream>
using namespace std;
#define maxn 1001
int dp[maxn]={0};
int input[maxn]={0};
int main(){
    int i,j,n;
    while(cin >> n){
        if(n==0)
            break;
        for(i=0;i<n;i++)
            cin >> input[i];
        for(i=0;i<n;i++){
            int tmp=0;
            for(j=0;j<i;j++){
                if(input[i]<=input[j])
                    continue;
                else{
                    tmp = max(tmp,dp[j]);
                }
            }
            dp[i] = tmp+input[i];
        }
        int tmp = 0;
        for(i=0;i<n;i++)
            tmp = max(tmp,dp[i]);
        cout << tmp << endl;

    }

}
  1. (HDU 1176)为了使问题简化,假设在接下来的一段时间里,馅饼都掉落在0-10这11个位置。开始时gameboy站在5这个位置,每秒种只有在移动不超过一米的范围内接住坠落的馅饼,因此在第一秒,他只能接到4,5,6这三个位置中其中一个位置上的馅饼。问gameboy最多可能接到多少个馅饼?(假设他的背包可以容纳无穷多个馅饼)

    思路:参考博客。这道题比较有趣。我感觉这道题考到了动规的几个条件中的 无后效性,即前面的决策不会影响后面的选择。如果从前往后看,前面步骤的选择由于每次只能移动一米这个限制条件,当前的决策是会影响后面的选择的;所以应该从后往前看,这样就不会有这个问题。另一个就是边界条件的判断,这里使用右移一位的方式很精巧。

#include <iostream>
#include <string.h>
using namespace std;
#define maxt 100001
#define maxs 13

int m,n,x,t,j,i;

int t_list[maxt][maxs]={0};
int dp[maxt][maxs]={0};
int main(){
    
    while(cin >> n && n){
        memset(dp,0,sizeof(dp));
        memset(t_list,0,sizeof(t_list));
        m= 0;
        for(i=0;i<n;i++){
            scanf("%d %d",&x,&t);
            t_list[t][x+1] += 1;
            m = max(m,t);
        }
        //dp[1] = t_list[4] + t_list[5] + t_list[6;
        for(i=m;i>=0;i--){
            for(j=1;j<12;j++)
                dp[i][j] = max(dp[i+1][j-1],max(dp[i+1][j],dp[i+1][j+1])) + t_list[i][j];
        }

        cout << dp[0][6] << endl;

    }
}

1.(HDU 1058)A number whose only prime factors are 2,3,5 or 7 is called a humble number. The sequence 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 24, 25, 27, ... shows the first 20 humble numbers.

思路:计算出钱5842个满足条件的数,难点在于如何按顺序计算;使用变量记录用了几个2,3,5,7

Write a program to find and print the nth element in this sequence

#include <iostream>
#include <string.h>
#define maxn 5843

using namespace std;



long long dp[maxn]={0,1};
int main(){
    int i,j,n;
    int  p2,p3,p5,p7;
    p2=p3=p5=p7=1;
    for(i=2;i<=maxn;i++){
        dp[i] = min(dp[p2]*2,min(dp[p3]*3,min(dp[p5]*5,dp[p7]*7)));
        if(dp[i]==dp[p2]*2)
            p2 ++;
        if(dp[i]==dp[p3]*3)
            p3 ++;
        if(dp[i]==dp[p5]*5)
            p5 ++;
        if(dp[i]==dp[p7]*7)
            p7 ++;
    }
    while(cin >> n && n){
         if(n%10==1&&n%100!=11)
                printf("The %dst humble number is %lld.\n",n,dp[n]);
            else if(n%10==2&&n%100!=12)
                printf("The %dnd humble number is %lld.\n",n,dp[n]);
            else if(n%10==3&&n%100!=13)
                printf("The %drd humble number is %lld.\n",n,dp[n]);
            else
                printf("The %dth humble number is %lld.\n",n,dp[n]);
    }
}

1.(HDU 1069)The researchers have n types of blocks, and an unlimited supply of blocks of each type. Each type-i block was a rectangular solid with linear dimensions (xi, yi, zi). A block could be reoriented so that any two of its three dimensions determined the dimensions of the base and the other dimension was the height.
They want to make sure that the tallest tower possible by stacking blocks can reach the roof. The problem is that, in building a tower, one block could only be placed on top of another block as long as the two base dimensions of the upper block were both strictly smaller than the corresponding base dimensions of the lower block because there has to be some space for the monkey to step on. This meant, for example, that blocks oriented to have equal-sized bases couldn't be stacked.

思路,参考。感觉这道题的问题和(HDU 1087)是一样的。首先,每种砖头可以有三个,保存时要把三种状态都存起来;
动态规划是一种有约束的优化问题?,这道题的优化目标就是最高的高度,约束条件就是下面的要比上面的大?如果按照这种思路,是否可以认为:

  • 动态规划里的状态就是要优化的目标
  • 约束条件是判断能不能进行转移的判断;
  • 要不要转移就是已知的优化目标加上当前的会不会更优?

刚开始看到这道题,知道要排序。但是排序是为了什么,怎么排,不清楚。所以这里的排序是为了理顺判断条件,以便于优化目标的计算?也就是说优化目标不需要排序,因为这个在状态转移的要不要中会涉及;

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

int dp[100]={0};

struct block{
    int x,y,z,h;
}blocks[100];

bool cmp(block a, block b){
    if(a.x==b.x)
        return a.y > b.y;
    return a.x > b.x;
}

int main(){
    int  n,i,j;
    int x,y,z;
    int indx = 0;
    while(cin >> n &&n){
        indx += 1;
        memset(dp,0,sizeof(dp));
        memset(blocks,0,sizeof(blocks));
        for(i=0,j=0;i<n;i++){
            cin >> x >> y >> z;
            blocks[j].x = min(x,y);
            blocks[j].y = max(x,y);
            blocks[j].z = z;

            blocks[j+1].x = min(x,z);
            blocks[j+1].y = max(x,z);
            blocks[j+1].z = y;

            blocks[j+2].x = min(z,y);
            blocks[j+2].y = max(z,y);
            blocks[j+2].z = x;

            j += 3;
        }
        n = j;
        sort(blocks,blocks+n,cmp);
        
        for(i=0;i<n;i++){
            dp[i] = blocks[i].z;
            for(j=i-1;j>=0;j--){
                if(blocks[j].x>blocks[i].x && blocks[j].y>blocks[i].y) // 能不能转移
                    dp[i] = max(dp[i],dp[j]+blocks[i].z); // 要不要转移
            }
        }
        int maxo = 0;
        for(i=0;i<n;i++)
            maxo = max(maxo,dp[i]);
        printf("Case %d: maximum height = %d\n",indx,maxo);
    }
}
  1. (HDU 2084)有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?

    5
    7
    3 8
    8 1 0
    2 7 4 4
    4 5 2 6 5

#include <iostream>
#include <cstring>
#include <math.h>
#define maxn 5656

using namespace std;
int  num[maxn]={0};
int dp[maxn]={0};
int  main(){
    int n,h,i,j;
    int tmp=0;
    cin >> n;
    while(n--){
        memset(dp,0,sizeof(dp));
        memset(num,0,sizeof(num));
        cin >> h;
        for(i=1;i<=h*(h+1)/2;i++)
            cin >> num[i];
        for(j=h*(h+1)/2;j>=1;j--){
            tmp = ceil((sqrt(1+8*j)-1)/2);
            dp[j] = max(dp[j+tmp],dp[j+tmp+1]) + num[j];
        }
        int out = 0;
        cout << dp[1] << endl;
    }
}
  1. (HDU 2059) 为了能够再赢兔子,乌龟不惜花下血本买了最先进的武器——“"小飞鸽"牌电动车。这辆车在有电的情况下能够以VT1 m/s的速度“飞驰”,可惜电池容量有限,每次充满电最多只能行驶C米的距离,以后就只能用脚来蹬了,乌龟用脚蹬时的速度为VT2 m/s。更过分的是,乌龟竟然在跑道上修建了很多很多(N个)的供电站,供自己给电动车充电。其中,每次充电需要花费T秒钟的时间。当然,乌龟经过一个充电站的时候可以选择去或不去充电。
    比赛马上开始了,兔子和带着充满电的电动车的乌龟并列站在起跑线上。你的任务就是写个程序,判断乌龟用最佳的方案进军时,能不能赢了一直以恒定速度奔跑的兔子。

    思路:这道题,变量很多。如果能想到动态规划的状态会相对容易。参考博客。最简单的暴力搜索是在每个站点停或不停,共有2^100种可能性。
    这里选择到达第 i 个充电站的最小时间为状态;感觉很巧妙

#include <iostream>
#include <cstring>
using namespace std;
#define maxn 100005
int main(){
    int L,i,j;
    int n,c,t,vr,vt1,vt2;
    int dist[maxn]={0};
    float dp[maxn]={0};
    int tmp=0;
    while(cin >> L){
        memset(dist,0,sizeof(dist));
        memset(dp,0,sizeof(dp));
        cin >> n >> c >> t;
        cin >> vr >> vt1 >> vt2;
        for(i=1;i<=n;i++)
            cin >> dist[i];
        tmp = c;
        dist[n+1] = L;
        float time1 = 0;
        for(i=1;i<=n+1;i++){
            dp[i]=maxn;
            for(j=0;j<i;j++){
                if(dist[i]-dist[j]>c)
                    time1 = c/(float)vt1 + (dist[i]-dist[j]-c)/(float)vt2;
                else
                    time1 = (dist[i]-dist[j])/(float)vt1;
            dp[i] = min(dp[i],dp[j]+time1+t*(j>0));
            }
        }
        float timer = (float)L/vr;
        if(timer<dp[n+1])
            printf("Good job,rabbit!\n");
        else
            printf("What a pity rabbit!\n");
    }  
}

转载于:https://www.cnblogs.com/curtisxiao/p/10570313.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值