diff --git a/TangDou/AcWing_TiGao/T1/LIS/272.md b/TangDou/AcWing_TiGao/T1/LIS/272.md index bea07bf..903fe59 100644 --- a/TangDou/AcWing_TiGao/T1/LIS/272.md +++ b/TangDou/AcWing_TiGao/T1/LIS/272.md @@ -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]$ **结尾** 的最长的上升序列长度。(以$a[i]$结尾的所有上升序列中属性为**最长**的那一个) +**状态表示** +$f[i]$表示从第一个数字开始算,以$a[i]$ **结尾** 的最长的上升序列长度。(以$a[i]$结尾的所有上升序列中属性为**最长**的那一个) **状态计算** @@ -57,7 +58,7 @@ f[i] = max(f[i], f[j] + 1) & 0 \le jb_k$$ +$$\large f[i][j]=max(f[i][j],f[i−1][k]+1),k∈[0,j−1],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 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_{i−1,k}+1) \ k∈[0,j−1],a_i=b_j,b_j>b_k$ +观察到,对于第二种状态转移:$f[i][j]=max(f[i][j],f[i−1][k]+1) \ k∈[0,j−1],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; } + ``` ### 五、空间优化 diff --git a/TangDou/AcWing_TiGao/T1/LIS/272_1.cpp b/TangDou/AcWing_TiGao/T1/LIS/272_1.cpp index 2695705..d08f917 100644 --- a/TangDou/AcWing_TiGao/T1/LIS/272_1.cpp +++ b/TangDou/AcWing_TiGao/T1/LIS/272_1.cpp @@ -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; diff --git a/TangDou/AcWing_TiGao/T1/LIS/272_2.cpp b/TangDou/AcWing_TiGao/T1/LIS/272_2.cpp index 88309d5..4f318c4 100644 --- a/TangDou/AcWing_TiGao/T1/LIS/272_2.cpp +++ b/TangDou/AcWing_TiGao/T1/LIS/272_2.cpp @@ -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); } } diff --git a/TangDou/AcWing_TiGao/T1/LIS/LIS+LCS专题.md b/TangDou/AcWing_TiGao/T1/LIS/LIS+LCS专题.md index 507c62e..a5b94a8 100644 --- a/TangDou/AcWing_TiGao/T1/LIS/LIS+LCS专题.md +++ b/TangDou/AcWing_TiGao/T1/LIS/LIS+LCS专题.md @@ -327,7 +327,113 @@ $q[2]:7$ **[$AcWing$ $187$. 导弹防御系统](https://www.acwing.com/problem/content/189/)** +导弹拦截系统的拦截高度即可以 严格单调上升,又可以严格单调下降,此时,用LIS模型是无法解决问题的,考虑到$n<50$,可以用$dfs+$剪枝 + +```cpp {.line-numbers} +#include + +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的全干掉了,能到这里的,都是= 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 + +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)