From 16c695e67c7f7d3afc5714f7730b2e5997ea6e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Wed, 6 Mar 2024 13:25:10 +0800 Subject: [PATCH] 'commit' --- TangDou/AcWing_TiGao/T1/LIS/1016.cpp | 2 +- TangDou/AcWing_TiGao/T1/LIS/1016.md | 3 +- TangDou/AcWing_TiGao/T1/LIS/LIS+LCS专题.md | 225 +++++++++++++++++-- 3 files changed, 205 insertions(+), 25 deletions(-) diff --git a/TangDou/AcWing_TiGao/T1/LIS/1016.cpp b/TangDou/AcWing_TiGao/T1/LIS/1016.cpp index 1084f43..5cabba0 100644 --- a/TangDou/AcWing_TiGao/T1/LIS/1016.cpp +++ b/TangDou/AcWing_TiGao/T1/LIS/1016.cpp @@ -5,7 +5,7 @@ using namespace std; int n; const int N = 100010; int a[N]; -int f[N]; +int f[N];//以第i个元素结尾的最大子序列和 int res; int main() { diff --git a/TangDou/AcWing_TiGao/T1/LIS/1016.md b/TangDou/AcWing_TiGao/T1/LIS/1016.md index 6499db0..c3a16b3 100644 --- a/TangDou/AcWing_TiGao/T1/LIS/1016.md +++ b/TangDou/AcWing_TiGao/T1/LIS/1016.md @@ -54,7 +54,6 @@ $f[i]$:以第$i$个元素结尾的最大子序列和 ### 三、$O(n^2)$实现代码 ```cpp {.line-numbers} -//运行时间: 31 ms #include using namespace std; @@ -62,7 +61,7 @@ using namespace std; int n; const int N = 100010; int a[N]; -int f[N]; +int f[N];//以第i个元素结尾的最大子序列和 int res; int main() { diff --git a/TangDou/AcWing_TiGao/T1/LIS/LIS+LCS专题.md b/TangDou/AcWing_TiGao/T1/LIS/LIS+LCS专题.md index 331b61f..a1c35b5 100644 --- a/TangDou/AcWing_TiGao/T1/LIS/LIS+LCS专题.md +++ b/TangDou/AcWing_TiGao/T1/LIS/LIS+LCS专题.md @@ -24,8 +24,18 @@ for (int i = 1; i <= n; i++) res = max(res, f[i]); **[$AcWing$ $896$. 最长上升子序列 II ](https://www.acwing.com/problem/content/898/)** -数据量增大:$N<=100000$ -$O(LogN*N)$算法 +数据量增大:$N<=100000$,$O(LogN*N)$算法 + +**状态表示** +$f[i]$表示长度为$i$的递增子序列中,末尾元素最小的是$f[i]$ + +**答案** +$fl$ + +**状态转移** +① $f[]$数组是一个单独上升的数组,这是可以二分的基础 +② 每个长度都争取替换掉前面记录数组中第一个大于等于自己的数字,以保证长度不变的情况下,数字最小,因为只有最小才能让后面的其它数字更容易接上去,机会才能更多。 + ```cpp {.line-numbers} // 1、首个元素入栈 @@ -55,18 +65,6 @@ for (int i = 1; i <= n; i++) { } ``` -很明显,第一种写法性能更高,第二种写法有点暴力。 - -**状态表示** -$f[i]$表示长度为$i$的递增子序列中,末尾元素最小的是$f[i]$ - -**答案** -$fl$ - -**状态转移** -① $f[]$数组是一个单独上升的数组,这是可以二分的基础 -② 每个长度都争取替换掉前面记录数组中第一个大于等于自己的数字,以保证长度不变的情况下,数字最小,因为只有最小才能让后面的其它数字更容易接上去,机会才能更多。 - **[$AcWing$ $897$. 最长公共子序列](https://www.acwing.com/problem/content/899/)** **状态表示** @@ -91,13 +89,196 @@ for (int i = 1; i <= n; i++) $f[n][m]$ #### 进阶题 -AcWing 1017. 怪盗基德的滑翔翼 -AcWing 1014. 登山 -AcWing 482. 合唱队形 -AcWing 1012. 友好城市 -AcWing 1016. 最大上升子序列和 -AcWing 1010. 拦截导弹 -AcWing 187. 导弹防御系统 -AcWing 272. 最长公共上升子序列 +**[$AcWing$ $1017$. 怪盗基德的滑翔翼](https://www.acwing.com/problem/content/1019/)** + +朴素$O(N^2)$版本 +```cpp {.line-numbers} +int mx = 0; +//从左到右,求一遍最长上升子序列 +for (int i = 1; i <= n; i++) { + f[i] = 1; + for (int j = 1; j < i; j++) + if (w[i] > w[j]) f[i] = max(f[i], f[j] + 1); + mx = max(mx, f[i]); +} +//反向求解 LIS问题 +for (int i = n; i >= 1; i--) { + f[i] = 1; + for (int j = n; j > i; j--) + if (w[i] > w[j]) f[i] = max(f[i], f[j] + 1); + mx = max(mx, f[i]); +} +``` +优化$O(LogN*N)$版本 +```cpp {.line-numbers} +// 正着求 +f[0] = a[0]; +for (int i = 1; i < n; i++) { + if (a[i] > f[fl]) + f[++fl] = a[i]; + else + *lower_bound(f, f + fl, a[i]) = a[i]; +} + +// 反着求 +g[0] = a[n-1]; +for (int i = n - 1; i >= 0; i--) { + if (a[i] > g[gl]) + g[++gl] = a[i]; + else + *lower_bound(g, g + gl, a[i]) = a[i]; +} +// pk的最大结果 +res = max(fl, gl); +// 输出 +printf("%d\n", res + 1); +``` + +**[$AcWing$ $1014$. 登山](https://www.acwing.com/problem/content/1016/)** +```cpp {.line-numbers} +//正向求解 LIS问题 +for (int i = 1; i <= n; i++) { + f[i] = 1; + for (int j = 1; j < i; j++) + if (a[i] > a[j]) f[i] = max(f[i], f[j] + 1); +} +//反向求解 LIS问题 +for (int i = n; i >= 1; i--) { + g[i] = 1; + for (int j = n; j > i; j--) + if (a[i] > a[j]) g[i] = max(g[i], g[j] + 1); +} +int res = 0; +//因为最终的那个中间点,左边计算了一次,右边双计算了一次,需要减1去重复 +for (int i = 1; i <= n; i++) res = max(res, f[i] + g[i] - 1); +``` + +**[$AcWing$ $482$. 合唱队形](https://www.acwing.com/problem/content/484/)** +```cpp {.line-numbers} +//正向求解 LIS问题 +for (int i = 1; i <= n; i++) { + f[i] = 1; + for (int j = 1; j < i; j++) + if (a[i] > a[j]) f[i] = max(f[i], f[j] + 1); +} +//反向求解 LIS问题 +for (int i = n; i >= 1; i--) { + g[i] = 1; + for (int j = n; j > i; j--) + if (a[i] > a[j]) g[i] = max(g[i], g[j] + 1); +} +int res = 0; +//因为最终的那个中间点,左边计算了一次,右边双计算了一次,需要减1去重复 +for (int i = 1; i <= n; i++) res = max(res, f[i] + g[i] - 1); +//输出 +printf("%d\n", n-res); +``` + +```cpp {.line-numbers} +//正向 +f[++fl] = a[1]; +p1[1] = 1; + +for (int i = 2; i <= n; i++) + if (a[i] > f[fl]) { + f[++fl] = a[i]; + p1[i] = fl; + } else { + int t = lower_bound(f + 1, f + fl + 1, a[i]) - f; + f[t] = a[i]; + p1[i] = t; + } + +//反向 +g[++gl] = a[n]; +p2[n] = 1; + +for (int i = n - 1; i >= 1; i--) + if (a[i] > g[gl]) { + g[++gl] = a[i]; + p2[i] = gl; + } else { + int t = lower_bound(g + 1, g + gl + 1, a[i]) - g; + g[t] = a[i]; + p2[i] = t; + } + +for (int i = 1; i <= n; i++) res = max(res, p2[i] + p1[i] - 1); +``` +**[$AcWing$ $1012$. 友好城市](https://www.acwing.com/problem/content/1014/)** +① 数对 +② 左端点排序 +③ 对于右端点组成的数组,求最长上升子序列长度 +```cpp {.line-numbers} +for (int i = 1; i <= n; i++)cin >> q[i].first >> q[i].second; + sort(q + 1, q + 1 + n);//按first排序 + int res = 0; + for (int i = 1; i <= n; i++) { + f[i] = 1; + for (int j = 1; j < i; j++) + if (q[i].second > q[j].second) + f[i] = max(f[i], f[j] + 1); + res = max(res, f[i]); + } + printf("%d\n", res); +``` + +```cpp {.line-numbers} +typedef pair PII; +#define x first +#define y second +PII a[N]; // 南岸和北岸的一对友好城市的坐标 +... +for (int i = 1; i <= n; i++) cin >> a[i].x >> a[i].y; + sort(a + 1, a + 1 + n); + + f[++fl] = a[1].y; + for (int i = 2; i <= n; i++) { + if (a[i].y > f[fl]) + f[++fl] = a[i].y; + else + *lower_bound(f + 1, f + 1 + fl, a[i].y) = a[i].y; + } + + printf("%d\n", fl); +``` + +**[$AcWing$ $1016$. 最大上升子序列和](https://www.acwing.com/problem/content/1012/)** + +```cpp {.line-numbers} +#include +using namespace std; + +// 最大上升子序列和 +int n; +const int N = 100010; +int a[N]; +int f[N];//以第i个元素结尾的最大子序列和 +int res; + +int main() { + cin >> n; + for (int i = 1; i <= n; i++) cin >> a[i]; + + for (int i = 1; i <= n; i++) { + f[i] = a[i]; // 最大上升子序列(个数)这里是1,此处是a[i] + for (int j = 1; j < i; j++) + // 最大上升子序列(个数)这里是加1,此处是+a[i] + if (a[i] > a[j]) f[i] = max(f[i], f[j] + a[i]); + res = max(res, f[i]); + } + printf("%d ", res); + return 0; +} +``` +**魔改 最长上升子序列和** + +**[$AcWing$ $1010$. 拦截导弹](https://www.acwing.com/problem/content/1012/)** + + +**[$AcWing$ $187$. 导弹防御系统](https://www.acwing.com/problem/content/189/)** + + +**[$AcWing$ $272$. 最长公共上升子序列](https://www.acwing.com/problem/content/274/)** [最长上升子序列 ($LIS$) 详解+例题模板 (全)](https://blog.csdn.net/lxt_Lucia/article/details/81206439)