main
黄海 1 year ago
parent 0e339b95e9
commit d28142e20f

@ -44,9 +44,10 @@ $1≤N≤3000$,序列中的数字均不超过 $2^{31}1$。
### 二、前导知识
[$AcWing$ $895$. 最长上升子序列](https://www.cnblogs.com/littlehb/p/15425546.html)
**[$AcWing$ $895$. 最长上升子序列](https://www.cnblogs.com/littlehb/p/15425546.html)**
**状态表示** $f[i]$表示从第一个数字开始算,以$a[i]$ **<font color='red'>结尾</font>** 的最长的上升序列长度。(以$a[i]$结尾的所有上升序列中属性为**最长**的那一个)
**状态表示**
$f[i]$表示从第一个数字开始算,以$a[i]$ **<font color='red'>结尾</font>** 的最长的上升序列长度。(以$a[i]$结尾的所有上升序列中属性为**最长**的那一个)
**状态计算**
@ -57,7 +58,7 @@ f[i] = max(f[i], f[j] + 1) & 0 \le j<i \ \& \ a[j]<a[i]
$$
[$AcWing$ $897$. 最长公共子序列](https://www.cnblogs.com/littlehb/p/15429296.html)
**[$AcWing$ $897$. 最长公共子序列](https://www.cnblogs.com/littlehb/p/15429296.html)**
定义$f[i][j]$是$a[]$以$i$结尾,$b[]$以$j$结尾的 **最长公共子序列长度**,但没有说$a[i]$或者$b[j]$一定要出现在最长公共子序列当中!这个最长公共子序列,可能是$a[]$和$b[]$的一些前序组成的,$a[i],b[j]$也可能没有对结果产生贡献。
@ -68,17 +69,6 @@ f[i][j]=max(f[i-1][j],f[i][j-1]) & a[i] \neq b[j]
\end{array}\right.
$
这是两个经典$DP$模型的结合版:
$LIS$ (最长上升子序列,$Longest$ $Increasing$ $Subsequence$)
$LCS$ (最长公共子序列,$Longest$ $Common$ $Subsequence$)
$LCIS$ (最长公共上升子序列,$Longest$ $Common$ $Increasing$ $Subsequence$)
$LCIS$ 也是一个 **相当经典** 的$DP$模型,他的 **状态分析** 是 $LIS$ 与 $LCS$ 的结合,且听我慢慢道来
### 三、本题解法
闫氏$DP$分析法:(结合了$LCS$与$LIS$的状态表示的方法,可以很直接的发现二者的影子)
@ -91,29 +81,14 @@ $LCIS$ 也是一个 **相当经典** 的$DP$模型,他的 **状态分析** 是
#### 状态转移
- $\large a_i \neq b_j$
考虑 $a$ 数组中前$i-1$个数字, $b$数组中前$j$个数字 ,且当前以 $b[j]$ 结尾的子序列的方案转移过来:
$$\large f_{i,j}=max(f_{i,j},f_{i1,j}), a_i \neq b_j$$
$$\large f[i][j]=max(f[i][j],f[i1][j])$$
- $\large a_i = b_j$
考虑 $a$ 数组中前$i-1$个数字, $b$数组中前 $k$ 个数字 ,且当前以 $b[k]$ 结尾的子序列的方案转移过来:
$$\large f_{i,j}=max(f_{i,j},f_{i1,k}+1),k∈[0,j1],a_i=b_j,b_j>b_k$$
$$\large f[i][j]=max(f[i][j],f[i1][k]+1),k∈[0,j1],a_i=b_j,b_j>b_k$$
如果直接按上述思路实现,需要三重循环:
按上述思路实现,需要三重循环:
```cpp {.line-numbers}
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= n; j ++ ){
f[i][j] = f[i - 1][j]; //不管a[i]是否等于b[j],f[i][j]一定会从f[i-1][j]继承过来
if (a[i] == b[j]){
int maxv = 1; //最起码命中了一个a[i]==b[j],LCIS最少是1
for (int k = 1; k < j; k ++ )
if (a[i] > b[k]) // 不光是公共,还要上升
maxv = max(maxv, f[i - 1][k] + 1);
f[i][j] = max(f[i][j], maxv);
}
}
}
```
#### 实现代码$O(N^3)$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
@ -122,7 +97,7 @@ const int N = 3010;
int a[N], b[N];
int f[N][N];
int res;
// 通过了 10/13个数据
// O(n^3)
int main() {
int n;
@ -138,14 +113,14 @@ int main() {
f[i][j] = f[i - 1][j];
// ③ 如果恰好 a[i]==b[j],那么就可以发生转移
if (a[i] == b[j]) {
int maxv = 1; // 最起码a[i]==b[j],有一个数字是一样嘀~
int mx = 1; // 最起码a[i]==b[j],有一个数字是一样嘀~
// f[i-1]是肯定的了问题是b的前驱在哪里需要枚举1~j-1
for (int k = 1; k < j; k++)
if (a[i] > b[k]) // 公共还不成,还需要上升
if (b[j] > b[k]) // 需要上升
// 找出公共且最长的
maxv = max(maxv, f[i - 1][k] + 1);
mx = max(mx, f[i - 1][k] + 1);
// 更新答案
f[i][j] = max(f[i][j], maxv);
f[i][j] = max(f[i][j], mx);
}
}
int res = 0;
@ -158,9 +133,9 @@ int main() {
### 四、优化
**$Q$:朴素办法超时($10/16$),如何优化?**
**$Q$:朴素办法超时($10/13$),如何优化?**
观察到,对于第二种状态转移:$f_{i,j}=max(f_{i,j},f_{i1,k}+1) \ k∈[0,j1],a_i=b_j,b_j>b_k$
观察到,对于第二种状态转移:$f[i][j]=max(f[i][j],f[i1][k]+1) \ k∈[0,j1],a_i=b_j,b_j>b_k$
每次用到的 **状态** 都是第 $i - 1$ 个阶段的
@ -186,11 +161,11 @@ int main() {
for (int i = 1; i <= n; i++) cin >> b[i];
for (int i = 1; i <= n; i++) {
int maxv = 1;
int mx = 1;
for (int j = 1; j <= n; j++) {
f[i][j] = f[i - 1][j];
if (a[i] == b[j]) f[i][j] = max(f[i][j], maxv);
if (a[i] > b[j]) maxv = max(maxv, f[i - 1][j] + 1);
if (a[i] == b[j]) f[i][j] = mx;
if (a[i] > b[j]) mx = max(mx, f[i - 1][j] + 1);
}
}
@ -199,6 +174,7 @@ int main() {
return 0;
}
```
### 五、空间优化

@ -5,7 +5,7 @@ const int N = 3010;
int a[N], b[N];
int f[N][N];
int res;
// 通过了 10/13个数据
// O(n^3)
int main() {
int n;
@ -21,14 +21,14 @@ int main() {
f[i][j] = f[i - 1][j];
// ③ 如果恰好 a[i]==b[j],那么就可以发生转移
if (a[i] == b[j]) {
int maxv = 1; // 最起码a[i]==b[j],有一个数字是一样嘀~
int mx = 1; // 最起码a[i]==b[j],有一个数字是一样嘀~
// f[i-1]是肯定的了问题是b的前驱在哪里需要枚举1~j-1
for (int k = 1; k < j; k++)
if (a[i] > b[k]) // 公共还不成,还需要上升
if (b[j] > b[k]) // 需要上升
// 找出公共且最长的
maxv = max(maxv, f[i - 1][k] + 1);
mx = max(mx, f[i - 1][k] + 1);
// 更新答案
f[i][j] = max(f[i][j], maxv);
f[i][j] = max(f[i][j], mx);
}
}
int res = 0;

@ -14,11 +14,11 @@ int main() {
for (int i = 1; i <= n; i++) cin >> b[i];
for (int i = 1; i <= n; i++) {
int maxv = 1;
int mx = 1;
for (int j = 1; j <= n; j++) {
f[i][j] = f[i - 1][j];
if (a[i] == b[j]) f[i][j] = maxv;
if (a[i] > b[j]) maxv = max(maxv, f[i - 1][j] + 1);
if (a[i] == b[j]) f[i][j] = mx;
if (a[i] > b[j]) mx = max(mx, f[i - 1][j] + 1);
}
}

@ -327,7 +327,113 @@ $q[2]:7$
**[$AcWing$ $187$. 导弹防御系统](https://www.acwing.com/problem/content/189/)**
导弹拦截系统的拦截高度即可以 严格单调上升又可以严格单调下降此时用LIS模型是无法解决问题的考虑到$n<50$$dfs+$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 55;
const int INF = 0x3f3f3f3f;
int n;
int a[N];
int up[N], down[N];
int res;
// 本题关键字:贪心+爆搜
/*
功能:暴搜所有可能,配合剪枝,找出最少的拦截系统数量
u: 第几个导弹
ul: 上升拦截系统的数量配合up 数组使用
dl: 下降拦截系统的数量配合down数组使用
*/
void dfs(int u, int ul, int dl) {
if (ul + dl >= res) return; // 伟大的剪枝不剪枝会TLE~,中途发现已经大于等于res的情况就返回
if (u == n) { // 走完全程,收集答案
res = min(res, ul + dl); // 因为上面的剪枝把ul+dl>=res的全干掉了能到这里的都是<res
return;
}
// 放入上升序列
int k = 0;
// 如果把当前导弹使用上升序列的拦截系统进行拦截,那个选择哪个系统呢?
for (k = 0; k < ul; k++)
if (up[k] <= a[u]) break; // up[0],up[1],up[2],... 这样套拦截系统,其数值来讲,是递减的,因为是因为不能再拦截高度更低的才创建了一套新的拦截系统
// 找出放到哪个拦截系统上去结果为k
int t = up[k]; // 尝试在第k套系统进行拦截第u个导弹,那么第u个导弹的高度就是第k套系统的新高度
up[k] = a[u]; // 问题是我们也不一定非得让第u个导弹使用上升序列拦截系统也可能使用下降序列拦截系统所以需要记录当前值回溯后尝试下降序列拦截
if (k < ul) //
dfs(u + 1, ul, dl); // 接到了某个队伍后面去了,修改队伍的最后最大值即可,队伍数量没有长大
else // 如果当前这些套拦截系统中无法找到某一套进行拦截
dfs(u + 1, ul + 1, dl); // 没有找到任何一个符合最后一个高度小于a[u]的队伍只能多开一队up数组长度长大1
up[k] = t; // 回溯
// ----------------------------------------------------------------------
// 放入下降序列
k = 0;
for (k = 0; k < dl; k++)
if (down[k] >= a[u]) break;
t = down[k]; // 保留现场
down[k] = a[u];
if (k < dl)
dfs(u + 1, ul, dl);
else
dfs(u + 1, ul, dl + 1);
down[k] = t; // 回溯
}
int main() {
while (cin >> n, n) { // 多套数据输入n=0时停止
for (int i = 0; i < n; i++) cin >> a[i];
res = INF; // 防御系统的最少数量
dfs(0, 0, 0); // 开始深搜更新res的值
printf("%d\n", res);
}
return 0;
}
```
**[$AcWing$ $272$. 最长公共上升子序列](https://www.acwing.com/problem/content/274/)**
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 3010;
int a[N], b[N];
int f[N][N];
int res;
// O(n^2)
int main() {
int n;
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++) {
int mx = 1;
for (int j = 1; j <= n; j++) {
f[i][j] = f[i - 1][j];
if (a[i] == b[j]) f[i][j] = mx;
if (a[i] > b[j]) mx = max(mx, f[i - 1][j] + 1);
}
}
for (int i = 1; i <= n; i++) res = max(res, f[n][i]);
printf("%d\n", res);
return 0;
}
```
[最长上升子序列 ($LIS$) 详解+例题模板 (全)](https://blog.csdn.net/lxt_Lucia/article/details/81206439)

Loading…
Cancel
Save