最长公共子序列与最长上升子序列--再学DP

最长公共子序列问题

给定两个字符串s1,s2...sn和t1,t2...tn.求出这两个字符串最长的字符串最长的公共子序列长度.

对于这个比较基础的DP的话,我们只需要简单打一个表,用来记录到对应长度的两个字符串的时候的状态,我们用一个两重循环来实现.

/*************************************************************************
    > File Name: 最长公共子序列.c
    > Author: zhanghaoran
    > Mail: 467908670@qq.com 
    > Created Time: 2015年06月07日 星期日 10时35分08秒
 ************************************************************************/

#include <stdio.h>
#include <string.h>

int m, n;
char s[1001];
char t[1001];
int dp[1001][1001];

int max(int a, int b){	
	return a > b ? a : b;
}
void solve(){
	int i, j;
	for(i = 0; i < n; i ++){
		for(j = 0; j < m; j ++){
			if(s[i] == t[j])
				dp[i + 1][j + 1] = max(dp[i][j] + 1, max(dp[i + 1][j], dp[i][j + 1]));
			else {
				dp[i + 1][j + 1] = max(dp[i + 1][j], dp[i][j + 1]);
			}
		}
	}
}
int main(void){
	int i, j;
	memset(dp, 0, sizeof(dp));
	scanf("%s", s);
	scanf("%s", t);
	n = strlen(s);
	m = strlen(t);
	solve();
	printf("%d\n", dp[n][m]);
}

这个算法来自于动态转移方程:

dp[i + 1][j + 1] =  ①max(dp[i][j+1], dp[i+1][j],dp[i][j]+1)   (s[i] = s[j])

      ②max(dp[i+1][j], dp[i][j+1])

打表如下:

好吧,我们再讨论一个最长上升子序列(LFS)的问题,这也是一个非常重要的DP应用:

题目为:有一个长度位n的数列a0,a1,...,an-1.求出这个序列中最长的上升子序列长度.

我们还是首先推一下动态转移方程吧.

dp[i] = max{dp[i], dp[j] + 1)  (j < i && a[j] < a[i])

把这个写作代码就是:

/*************************************************************************
    > File Name: 最长上升子序列问题.c
    > Author: zhanghaoran
    > Mail: 467908670@qq.com 
    > Created Time: 2015年06月07日 星期日 14时14分02秒
 ************************************************************************/

#include <stdio.h>
#include <string.h>

int n;
int a[10010];
int dp[10010];
#define INF 100000
int max(int a, int b){
	return a > b ? a : b;
}

void solve(){
	int i, j;
	int res = 0;
	for(i = 0; i < n; i ++){
		dp[i] = 1;
		for(j = 0; j < i; j ++){
			if(a[i] > a[j])
				dp[i] = max(dp[i], dp[j] + 1);
		}
		res = max(res, dp[i]);
	}
	printf("%d\n", res);
}

int main(void){
	int i;
	scanf("%d", &n);
	for(i = 0; i < n; i ++)
		scanf("%d",&a[i]);
	solve();
}
比较好理解,就是将在i之前的数字中小于当前数字的动态规划数组中存有的最大值自加1存在当前数字的动态规划数组中

在挑战程序设计一书中提出了一种另外的方法:

void solve(){
	int i;
	fill(dp, dp + n, INF);
	for(i = 0; i < n; i ++)
		*lower_bound(dp, dp + n, a[i]) = a[i]; 
	printf("%d\n",lower_bound(dp, dp + n, INF) - dp);
}

这里的lower_bound是一个STL(标准模板库)函数,他的作用是从已经排好序的数列中利用二分搜索找到指向ai >= k 的最小的ai的指针.

这个的算法思想是让dp中的值先置位极大,然后向后遍历数组dp,出现小于dp中的数字的情况的话,将当前数组元素置位a[i],最后输出a数组的有效长度即可,这个STL函数很好用,以后要多加以学习.

说了两个比较基础的,那就直接上一道题吧.

I - 最少拦截系统
Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u

Description

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能超过前一发的高度.某天,雷达捕捉到敌国的导弹来袭.由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹. 
怎么办呢?多搞几套系统呗!你说说倒蛮容易,成本呢?成本是个大问题啊.所以俺就到这里来求救了,请帮助计算一下最少需要多少套拦截系统. 
 

Input

输入若干组数据.每组数据包括:导弹总个数(正整数),导弹依此飞来的高度(雷达给出的高度数据是不大于30000的正整数,用空格分隔) 
 

Output

对应每组数据输出拦截所有导弹最少要配备多少套这种导弹拦截系统. 
 

Sample Input

8 389 207 155 300 299 170 158 65
 

Sample Output

2
 
这个题的想法可以有很多,我写了三种方法来分享一下:

int n;
int a[10001];
int dp[10001];
int res = 1;

int main(void){
	int i, j;
	while(scanf("%d", &n) != EOF){
		dp[1] = 0;
		for(i = 1; i <= n; i ++){
			scanf("%d", &a[i]);
			for(j = 1; j <= res; j ++){
				if(a[i] < a[j]){
					dp[j] = a[i];
					break;
				}
				else if(j == res){
					res ++;
					dp[res] = a[i];
					break;
				}
			}
		}
		printf("%d\n", res - 1);
		res = 1;
	}
}
这个的方法是用dp存储几套系统当前可以打到的最低高度,比如给出一组输入数据5,300,125,325,64,78,那么他的工作就是在遍历到300时将300存入dp[1],计数器res自加,在125时更改dp[1]为125,在325时将325存入dp{2],res自加,64存入时将dp[1]更新为64,最后将dp[2],更新为78,res自加两次,一共需要两套系统.


int n;
int a[10001];
int dp[10001];

int main(void){
	int i, j;
	int max = 0;
	while(scanf("%d", &n) != EOF){
		for(i = 1; i <= n; i ++){
			scanf("%d", &a[i]);
			dp[i] = 1;
			for(j = 1; j < i; j ++){
				if(a[i] >= a[j])
					if(dp[i] <= dp[j] + 1)         //保证是上升子序列的个数
						dp[i] = dp[j] + 1;
			}
			max = max > dp[i] ? max : dp[i];
		}
		printf("%d\n", max);
		max = 0;
	}
}
这个方法类似上面求最长上升子序列,是将dp用来存储当前数字需要的系统数目.继续拿上面的例子举例,300时跳过,125时不满足条件跳过,325时是2,最后输出的最大值就是2


int n;
int a[10010];
int flag[10010];
int main(void){
	int i;
	int temp, t;
	int res = 0;
	while(scanf("%d", &n) != EOF){
		for(i = 0; i < n; i ++)
			scanf("%d", &a[i]);
		memset(flag, 0, sizeof(flag));
		for(i = 0; i < n; i ++){
			if(flag[i])
				continue;
			else res ++;
			flag[i] = 1;
			flag[0] = 1;
			temp = i + 1;
			t = a[i];
			while(temp != n){
				if( !flag[temp] && a[temp] <= t){
					t = a[temp];
					flag[temp] = 1;
				}
				temp ++;
			}
		}
		printf("%d\n", res);
	}
}

这个就是利用t来存储当前情况中能打到的最小高度,然后打中以后将其位置标记,每再次更新最高值的话,令res自加1.

当然还有其他的方法,比如可以对其进行快排(从低到高),然后求有序数列和原数列的最长公共子序列长度即可.


posted @ 2015-06-07 19:05  ChiLuManXi  阅读(183)  评论(0编辑  收藏  举报