做到一题,顺便总结一下 LIS LCS
先给出一道 n2 的 DP 做法求LIS
原题链接:
Gym 101653 O Diamonds
大意:
给一个序列,每个点代表一个项链,有两个值 w,c 要求最长上升子序列,只不过现在要同时考虑两个
思路:
题目的范围很宽,直接n2算法能直接过,一开始写nlogn算法不知道为什么会错,好像是因为重载过后 lower_bound仍然不能用,有别于一维的做法.
直接n2 DP做法
DP方程:
f(i)=max{f(j)+1,f(i)}(j<i,aj<ai)
f
(
i
)
=
m
a
x
{
f
(
j
)
+
1
,
f
(
i
)
}
(
j
<
i
,
a
j
<
a
i
)
具体代码:
//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<string>
#include<cstring>
using namespace std;
#define INF 0x3f3f3f3f
#define M(a, b) memset(a, b, sizeof(a))
const int MAXN = 2000 + 5;
struct node {
double w, c;
} a[MAXN];
int dp[MAXN];
int main() {
int T;
cin >> T;
while(T--) {
int n;
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i].w >> a[i].c;
dp[i]=1;
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j < i; j++) {
if(a[i].w>a[j].w && a[i].c<a[j].c) dp[i]=max(dp[j]+1,dp[i]);
}
}
int ans=0;
for(int i=1;i<=n;i++) ans=max(ans,dp[i]);
cout<<ans<<endl;
}
}
一维的DP做法和这个同理,区别只在于”大小于号的重载”
nlogn做法:
详见:http://blog.youkuaiyun.com/shuangde800/article/details/7474903
定义d[k]:长度为k的上升子序列的最末元素,若有多个长度为k的上升子序列,则记录最小的那个最末元素。
注意d中元素是单调递增的,下面要用到这个性质。
首先len = 1,d[1] = a[1],然后对a[i]:若a[i]>d[len],那么len++,d[len] = a[i];
否则,我们要从d[1]到d[len-1]中找到一个j,满足d[j-1]
用lower_bound函数实现
关键代码:
for(int i=0;i<n;i++){
if(a[i]>=dp[len])
dp[++len]=a[i];
else {
int p=lower_bound(dp,dp+len,a[i])-dp;
dp[p]=a[i];
}
}
printf("%d\n",len);
else 里面更新的意义在于为后面的服务,
可能存在序列{1,3,7,9} 遇到 6 需要改为 {1,3,6,9} 那么后面再遇到 7,8 就能修改为 {1,3,6,7,8}
另外一种 n2 做法 排序+LCS (Longest Commen Sequence) 最长公共子序列
LCS的复杂度为 M*N
方法很好理解.
LCS的做法需要理解三个转移方程:
详见:
http://blog.youkuaiyun.com/hrn1216/article/details/51534607
状态转移方程:
输出LCS:
倒推回去,从末尾开始输出,选择c大的一个方向逆推.