小学期学的算法和做OJ题的各种经验总结
最长公共子序列 LCS:
正着遍历,倒着构造
c[i,j]表示:(x1,x2....xi) 和 (y1,y2...yj) 的最长公共子序列的长度
序列回溯,可以用栈。
//dp初始化为0
for(i=1;i<=lena;i++){
for(j=1;j<=lenb;j++){
if(a[i-1]==b[j-1]){
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[lena][lenb]);
最长上升子序列 LIS
参考:https://blog.youkuaiyun.com/lxt_Lucia/article/details/81206439
DP: O(n2)
状态设计:F[i] 代表以 A[i] 结尾的 LIS 的长度
边界处理:F [ i ] = 1 (1 <= i <= n)
状态转移:遍历i之前的数中找到所有比A[i] 小的数的位置j,如果没有就是自身
F
i
=
{
max
(
F
j
+
1
)
,
j
=
1
⋯
i
−
1
且
A
j
<
A
i
F
i
(
初
始
化
时
为
1
)
,
第
一
个
式
子
没
有
满
足
的
条
件
,
即
max
(
F
j
+
1
)
=
1
,
则
自
己
为
序
列
开
头
F_i = \begin{cases} \max(F_j + 1 ), & j = 1\cdots i - 1且A_j < A_i \\ F_i(初始化时为1),& 第一个式子没有满足的条件,\\&即\max(F_j+1)=1,则自己为序列开头 \end {cases}
Fi=⎩⎪⎨⎪⎧max(Fj+1),Fi(初始化时为1),j=1⋯i−1且Aj<Ai第一个式子没有满足的条件,即max(Fj+1)=1,则自己为序列开头
综合起来就变成了
F
i
=
max
{
F
j
+
1
,
F
i
∣
w
h
e
r
e
1
≤
j
≤
i
−
1
a
n
d
A
j
<
A
i
}
F_i = \max\{F_j+1,\space F_i \ | \ where\ 1\le j \le i-1 \ and \ A_j \lt A_i\}
Fi=max{Fj+1, Fi ∣ where 1≤j≤i−1 and Aj<Ai}
ans即为以每个数为结尾的最长子序列中最大的
代码
for (int i = 1; i <= n; i++) f[i] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j < i; j++)
if (a[j] < a[i])
f[i] = max(f[i], f[j] + 1);
for (int i = 1; i <= n; i++)
ans = max(ans, f[i]);
贪心+二分O(nlogn)
low 数组,lowm 表示长度为m的LIS结尾元素的最小值(初始化为INF),对于每一个长度为m的LIS,一定是结尾元素越小越有潜力拼的更长。因此我们维护low数组,用len记录LIS当前的长度,对于每一个ai ,
- 如果ai > lowlen 就拼到LIS里,即令low++len=ai
- 如果ai < lowm(m是满足条件 lowk > ai 的第一个位置) ,说明ai作为长度为m的LIS的结尾元素更有潜力,就把ai 跟lowm替换。
- 找满足条件的第一个位置可以利用二分搜索,
- 可以直接用STL函数lower_bound(默认小于比较)
- 用法:lower_bound(first, last, k[, cmp]) 返回first到last中不满足*it<k或cmp(*it,k)的第一个位置(地址),默认情况下即返回数组中第一个大于k的数的地址;
- int m = lower_bound(low+1, low+1+len, a[i]) - low;
地址 - 地址 = 下标
代码:
int main() {
int low[MAXN] = INF;//长度为m的LIS结尾元素的最小值
int a[MAXN], n;
//初始条件
low[1] = a[i];
int len=1;
for (int i = 2; i <= n; i++){
if(a[i]>low[len]) low[++len]=a[i];//拼入LIS
else{
//找到第一个大于a[i]的位置,更新成a[i]
int m = lower_bound(low+1,low+1+n,a[i]) - low;
low[m] = a[i];
}
}
cout<<len;
return 0;
}