vlambda博客
学习文章列表

动态规划算法经典问题回顾(1)

本文面向的是具有动态规划基础的编程友们,还没了解过动规的可戳原文链接入门动态规划算法(引用自百度百家号)。



01


Longest-Increasing-Subsequence (LIS)


题目描述

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。


输入导弹依次飞来的高度(雷达给出的高度数据是 ≤ 50000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。


输入格式

1行,若干个整数(个数≤100000)


输出格式

2行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。


输入输出样例

输入 

389 207 155 300 299 170 158 65


输出

6

2


#include <stdio.h>#include <algorithm>#define N 100010using namespace std;int a[N];int dp[N];int dp2[N];
int main() { int x; int n = 0; while(~scanf("%d",&x)) { a[n++] = x; } reverse(a,a+n); dp[0] = a[0]; int L = 1; for(int i = 1; i < n; i++) { if(dp[L-1] <= a[i]) { dp[L++] = a[i]; } else { *upper_bound(dp,dp+L,a[i]) = a[i]; } } printf("%d\n",L);
reverse(a,a+n); dp2[0] = a[0]; int cnt = 1; for(int i = 1; i < n; i++) { if(dp2[cnt-1] < a[i]) { dp2[cnt++] = a[i]; } else { *lower_bound(dp2,dp2+cnt,a[i]) = a[i]; } } printf("%d\n",cnt); return 0;}


02


一道实质是LIS的Longest-Common-Subsequence(LCS)


题目描述

给出1-n的两个排列P1和P2,求它们的最长公共子序列。


输入格式

第一行是一个数n,


接下来两行,每行为n个数,为自然数1-n的一个排列。


输出格式

一个数,即最长公共子序列的长度


输入输出样例

输入

3 2 1 4 5

1 2 3 4 5


输出

3


说明/提示

【数据规模】

对于50%的数据,n≤1000;对于100%的数据,n≤100000


直接打LCS:

#include <stdio.h>#define N 10005 //再开一个数量级程序就崩了#include <algorithm>#include <iostream>using namespace std;
int dp[N][N];char a[N],b[N];int n;
int main() { cin >> n; for(int i = 1; i <= n; i++) cin >> a[i]; for(int i = 1; i <= n; i++) cin >> b[i]; for(int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++) { if(a[i] == b[j]) { dp[i][j] = dp[i-1][j-1]+1; } else { dp[i][j] = max(dp[i-1][j],dp[i][j-1]); } } } printf("%d\n",dp[n][n]); return 0;}
(肯定过不了,时空都有问题,即使使用滚动数组优化空间,时间也还是个问题)

这道题其实用了排列的性质,思路是这样的:
样例输入第二行:3 2 1 4 5
样例输入第三行:1 2 3 4 5

我们不妨以第二行为基准,用map映射:
map[3] = 1map[2] = 2map[1] = 3map[4] = 4map[5] = 5// 每个数映射到它的index

那么,第二行的数实际上就成了1-n排列的一个数列。

我们把刚才的map映射到第三行,原问题可以转化为求解第三行的最长上升子序列。


参考代码:

#include <stdio.h>#include <map>#include <algorithm>#define N 100010using namespace std;int dp[N];int a[N];
int main() { int n; scanf("%d",&n); int x; map<int,int> mp; for(int i = 0; i < n; i++) { scanf("%d",&x); mp[x] = i; } for(int i = 0; i < n; i++) { scanf("%d",&x); a[i] = mp[x]; } dp[0] = a[0]; int L = 1; for(int i = 1; i < n; i++) { if(dp[L-1] <= a[i]) { dp[L++] = a[i]; } else { *upper_bound(dp,dp+L,a[i]) = a[i]; } } printf("%d\n",L); return 0;}



03


最大连续子段和


题目描述

给出一段序列,选出其中连续且非空的一段使得这段和最大。


输入格式

第一行是一个正整数N,表示了序列的长度。


第二行包含N个绝对值不大于10000的整数Ai,描述了这段序列。


输出格式

一个整数,为最大的子段和是多少。子段的最小长度为1。


输入输出样例

输入

7

2 -4 3 -1 2 -4 3


输出

4


说明/提示


【样例说明】


2,-4,3,-1,2,-4,3中,最大的子段和为4,该子段为3,-1,2.


【数据规模与约定】


对于40%的数据,有N ≤ 2000。


对于100%的数据,有N≤200000。


// 换种语言,Java语言import java.util.Scanner;
public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = in.nextInt(); int[] a = new int[n+1]; for(int i = 1; i <= n; i++) a[i] = in.nextInt(); int maxx = -0x7f7f7f7f; int[] dp = new int[n+1]; for(int i = 1; i <= n; i++) { if(dp[i-1] >= 0) dp[i] = dp[i-1] + a[i]; else dp[i] = a[i]; if(dp[i] > maxx) maxx = dp[i]; } System.out.println(maxx); }}