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
分析:
思路:
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
分析:
思路:
代码:
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。
分析:
•关键词:左上角进,右下角出。不能对角穿
思路:
代码:
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)
总结:
同上
动态规划个人体会与思考:
动态规划的特点:
- 把原始问题划分成一系列子问题;
- 求解每个子问题仅一次,并将其结果保存在一个表中,以后用到时直接存取,不重复计算,节省计算时间
- 自底向上地计算。
- 整体问题最优解取决于子问题的最优解(状态转移方程)(将子问题称为状态,最终状态的求解归结为其他状态的求解)
所以运用好动态规划,可以用来解决数量级很大的问题,将问题分解为规模更小的子问题,直到子问题的解显而易见之后返回求解,
而自底向上的填表方法完美的省去了递归的所需要开辟的时间以及空间。