7-1 最大子段和 (25 分)

给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时,定义子段和为0。

要求算法的时间复杂度为O(n)。

输入格式:

输入有两行:

第一行是n值(1<=n<=10000);

第二行是n个整数。

输出格式:

输出最大子段和。

输入样例:

在这里给出一组输入。例如:

6
-2 11 -4 13 -5 -2
 
结尾无空行

输出样例:

在这里给出相应的输出。例如:

20
 
结尾无空行

  分析:

•此题关键词:负数、子段和、最大值、时间复杂度O(n)
•题目要求返回子段数值之和的最大值

  思路:

•我们用一个数组a来装题目数组,规定一个数组b,b[i]代表以第i个元素为终点的最大子段和
•这样子我们求b[i]就可以利用b[i-1]的值来判断,假设b[i-1] + a[i] < a[i], 即以i为终点的最大子段和加上当前i下标的值比当前下标i所对应的值小,那么说明以i-1为终点的子段已经达到最大值,重新以a[i]为开始继续寻找,最后b数组填表完毕,遍历b数组,找到b数组中的最大值即为答案(如果比0小则刷新为0)。

  代码:
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int a[10001], b[10001];
 4 int main()
 5 {
 6   int n;
 7   cin>>n;
 8   for(int i=0;i<n;i++)
 9    cin>>a[i];
10    int maxx=a[0];
11    b[0]=a[0]>0?a[0]:0;
12    for(int i=1;i<n;i++)
13    {
14         b[i]=max(b[i-1]+a[i],a[i]);
15         maxx=max(maxx,b[i]);
16    } 
17     if(maxx < 0) maxx = 0;//因为有可能这个数组都是负数导致maxx<0
18    cout<<maxx<<endl;
19    return 0;
20 }

  时间复杂度:

一层for循环,与n有关,故为O(n)

  总结:

运用了动态规划的思想,每个问题都分解为更小规模,更好讨论的子问题,建表求解。


 

7-2 单调递增最长子序列 (25 分)

设计一个O(n2)时间的算法,找出由n个数组成的序列的最长单调递增子序列。

输入格式:

输入有两行: 第一行:n,代表要输入的数列的个数 第二行:n个数,数字之间用空格格开

输出格式:

最长单调递增子序列的长度

输入样例:

在这里给出一组输入。例如:

5
1 3 5 2 9
 
结尾无空行

输出样例:

在这里给出相应的输出。例如:

4
 
结尾无空行

  分析:

•题目要求返回最长递增子段长度

  思路:

•我们同样用一个数组a来装题目数组,规定一个数组dp,dp[i]代表以第i个元素为终点的最长递增序列长度
•而dp[i]的求法是从i-1个下标开始,向前遍历,找到比a[j] > a[i] 且 dp[j]的值比dp[i]要大,则刷新dp[i]

  代码:

 1 #include<iostream>
 2 using namespace std;
 3 int main(){
 4     int n,a[1000];
 5     cin>>n;
 6     for(int i=1;i<=n;i++)
 7         cin>>a[i];
 8     int dp[1000]={0},max=1,t;
 9     dp[1]=1;
10     for(int i=2;i<=n;i++){
11         t=0;//每次都要置零,很关键
12         for(int j=i-1;j>0;j--){
13             if(a[i]>a[j]&&dp[j]>t){
14                 t=dp[j];//把符合条件的值暂时保存在t中
15             }
16         }
17         dp[i]=dp[i]+t+1;
18         if(dp[i]>max)
19             max=dp[i];
20     }
21     cout<<max<<endl;
22     return 0;
23 }

 

  时间复杂度:

为递增数列和 1+2+3+...+n = (n+1)*n/2,所以为O(n)

  总结:

同上


 

7-3 最低通行费 (25 分)

一个商人穿过一个N×N的正方形的网格,去参加一个非常重要的商务活动。他要从网格的左上角进,右下角出。每穿越中间1个小方格,都要花费1个单位时间。商人必须在(2N-1)个单位时间穿越出去。而在经过中间的每个小方格时,都需要缴纳一定的费用。

这个商人期望在规定时间内用最少费用穿越出去。请问至少需要多少费用?

注意:不能对角穿越各个小方格(即,只能向上下左右四个方向移动且不能离开网格)。

输入格式:

第一行是一个整数,表示正方形的宽度N (1≤N<100);

后面N行,每行N个不大于100的整数,为网格上每个小方格的费用。

输出格式:

至少需要的费用。

输入样例:

5
1  4  6  8  10 
2  5  7  15 17 
6  8  9  18 20 
10 11 12 19 21 
20 23 25 29 33
 
结尾无空行

输出样例:

109
 
结尾无空行

样例中,最小值为109=1+2+5+7+9+12+19+21+33。


 

分析:

•关键词:左上角进,右下角出。不能对角穿

•题目要求返回通过的最低路费

  思路:

•每次移动只有两种方案:向右,向下,我们可以规定low_fare(i, j)表示从位置(i, j)到位置(n, n)的最低费用
•而low_fare(i, j)可以通过low_fare(i+1, j)与low_fare(i, j+1)之间的较小值与当前路费fare[i][j]相加求得

  代码:

 

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int fare[200][200];
 4 int num;
 5 int mem[200][200];//备忘录缩短运行时间,减少重复计算
 6 int min(int a, int b){
 7     if(a > b) return b;
 8     else return a;
 9 }
10 int low_fare(int i, int j){
11     if(mem[i][j]) return mem[i][j];//假如曾经计算过从i到j的最低路费,则直接用备忘录中的值
12     if(i == num && j == num) return fare[num][num];
13     int min1 =  fare[i][j] + min(low_fare(i, j+1), low_fare(i+1, j));
14     mem[i][j] = min1;
15     return min1;
16 }
17 
18 int main(){
19     cin >> num;
20     for(int i = 0; i <= 200; i++){
21         for(int j = 0; j <= 200; j++){
22             fare[i][j] = 10000000;
23         }
24     }
25     for(int i = 1; i <= num; i++){
26         for(int j = 1; j <= num; j++){
27             cin >> fare[i][j];
28         }
29     }
30     cout << low_fare(1,1) << endl;
31 }

 


 

 

 

 

  时间复杂度:

O(n2)

  总结:

同上


动态规划个人体会与思考:

动态规划的特点:

 

  • 把原始问题划分成一系列子问题;
  • 求解每个子问题仅一次,并将其结果保存在一个表中,以后用到时直接存取,不重复计算,节省计算时间
  • 自底向上地计算。
  • 整体问题最优解取决于子问题的最优解(状态转移方程)(将子问题称为状态,最终状态的求解归结为其他状态的求解)

所以运用好动态规划,可以用来解决数量级很大的问题,将问题分解为规模更小的子问题,直到子问题的解显而易见之后返回求解,

而自底向上的填表方法完美的省去了递归的所需要开辟的时间以及空间。

 

 

posted on 2021-10-25 20:39  Aheador  阅读(205)  评论(0编辑  收藏  举报