最长公共子序列问题
给定两个字符串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函数很好用,以后要多加以学习.
说了两个比较基础的,那就直接上一道题吧.
Description
怎么办呢?多搞几套系统呗!你说说倒蛮容易,成本呢?成本是个大问题啊.所以俺就到这里来求救了,请帮助计算一下最少需要多少套拦截系统.
Input
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.
当然还有其他的方法,比如可以对其进行快排(从低到高),然后求有序数列和原数列的最长公共子序列长度即可.