'commit'
This commit is contained in:
0
TangDou/AcWing/DP/LIS/0最长上升子序列.txt
Normal file
0
TangDou/AcWing/DP/LIS/0最长上升子序列.txt
Normal file
55
TangDou/AcWing/DP/LIS/ErFenTest.cpp
Normal file
55
TangDou/AcWing/DP/LIS/ErFenTest.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
const int N = 6;
|
||||
int a[N] = {1, 2, 2, 3, 3, 4};
|
||||
|
||||
//模拟STL中的lower_bound,左闭右开写法
|
||||
// int lower_bound(int x) {
|
||||
// int l = 0, r = 7; //左闭右开
|
||||
// while (l <= r) {
|
||||
// int mid = (l + r) >> 1;
|
||||
// if (x > a[mid])
|
||||
// l = mid + 1;
|
||||
// else
|
||||
// r = mid - 1;
|
||||
// }
|
||||
// return l;
|
||||
// }
|
||||
|
||||
int upper_bound(int x) {
|
||||
int l = 0, r = N;
|
||||
while (l <= r) {
|
||||
int mid = l + ((r - l) >> 1);
|
||||
if (x >= a[mid]) // upper的话等于的时候也返 //回右边,因为要找到大于最后一个key。
|
||||
l = mid + 1;
|
||||
else
|
||||
r = mid - 1;
|
||||
}
|
||||
return l; //记得返回L。
|
||||
}
|
||||
|
||||
int lower_bound(int x) {
|
||||
int l = 0, r = N;
|
||||
while (l <= r) {
|
||||
int mid = l + ((r - l) >> 1);
|
||||
if (x > a[mid])
|
||||
l = mid + 1;
|
||||
else // lower相等时候就返回左边,因为要
|
||||
//找到第一个key。
|
||||
r = mid - 1;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
int main() {
|
||||
printf("%d\n", lower_bound(3));
|
||||
printf("%d\n", upper_bound(3) - 1);
|
||||
|
||||
printf("%d\n", lower_bound(4));
|
||||
printf("%d\n", upper_bound(4) - 1);
|
||||
|
||||
printf("%d\n", lower_bound(5));
|
||||
printf("%d\n", upper_bound(5) - 1);
|
||||
return 0;
|
||||
}
|
||||
76
TangDou/AcWing/DP/LIS/LCIS.md
Normal file
76
TangDou/AcWing/DP/LIS/LCIS.md
Normal file
@@ -0,0 +1,76 @@
|
||||
https://blog.csdn.net/CUCUC1/article/details/113706736
|
||||
|
||||
## 最长公共上升子序列($LCIS$)
|
||||
|
||||
### 前置知识
|
||||
#### $LCS$
|
||||
$$\LARGE f[i][j]= \left\{\begin{matrix}
|
||||
f[i-1][j-1]+1& i,j>0,a[i]=b[j] \\
|
||||
max(f[i,j-1],f[i-1][j])& i,j>0,a[i]\neq b[j]
|
||||
\end{matrix}\right.
|
||||
$$
|
||||
|
||||
#### $LIS$
|
||||
自行脑补
|
||||
|
||||
---
|
||||
|
||||
注意:
|
||||
刚开始看这个问题的时候,第一反应是先求出$LCS$再求出$LCS$的$LIS$,事实上这是有问题的,我们并不能保证这么求出的$LCIS$是最长的,比如下面这个例子:
|
||||
|
||||
> Example
|
||||
a:[7 1 5 6 4 2 7]
|
||||
b:[7 1 5 4 6 7 2]
|
||||
按照递归的取**最长公共子序列**,取出:
|
||||
[7 1 5 6 2]
|
||||
此序列的**最长上升子序列**为:
|
||||
1 5 6 (len=3)
|
||||
但原序列的“最长公共上升子序列”为:
|
||||
1 5 6 7 (len=4)
|
||||
|
||||
<font color='red' size=4><b>小结:</b></font>
|
||||
<font color='blue' size=4><b>证明一个假设是错的很简单,直接找一个反例就行了;证明一个假设是正确的很难,因为你要证明所有情况都是对的。</b></font>
|
||||
|
||||
|
||||
这道题目是$AcWing$ $895$. 最长上升子序列和$AcWing$ $897$. 最长公共子序列的结合版,在状态表示和状态计算上都是融合了这两道题目的方法。
|
||||
|
||||
状态表示:
|
||||
|
||||
* `f[i][j]`代表所有`a[1 ~ i]`和`b[1 ~ j]`中以`b[j]`结尾的公共上升子序列的集合;
|
||||
|
||||
* `f[i][j]`的值等于该集合的子序列中长度的最大值;
|
||||
|
||||
状态计算(对应集合划分):
|
||||
|
||||
首先依据公共子序列中是否包含`a[i]`,将`f[i][j]`所代表的集合划分成两个不重不漏的子集:
|
||||
|
||||
* 不包含`a[i]`的子集,最大值是`f[i - 1][j]`;
|
||||
* 包含`a[i]`的子集,将这个子集继续划分,依据是子序列的倒数第二个元素在`b[]`中是哪个数:
|
||||
* 子序列只包含`b[j]`一个数,长度是`1`;
|
||||
* 子序列的倒数第二个数是`b[1]`的集合,最大长度是`f[i - 1][1] + 1`;
|
||||
* …
|
||||
* 子序列的倒数第二个数是`b[j - 1]`的集合,最大长度是`f[i - 1][j - 1] + 1`;
|
||||
|
||||
如果直接按上述思路实现,需要三重循环:
|
||||
```c++
|
||||
for (int i = 1; i <= n; i ++ ){
|
||||
for (int j = 1; j <= n; j ++ ){
|
||||
f[i][j] = f[i - 1][j];
|
||||
if (a[i] == b[j]){
|
||||
int maxv = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后我们发现每次循环求得的`maxv`是满足`a[i] > b[k]`的`f[i - 1][k] + 1`的前缀最大值。
|
||||
因此可以直接将`maxv`提到第一层循环外面,减少重复计算,此时只剩下两重循环。
|
||||
|
||||
最终答案枚举子序列结尾取最大值即可。
|
||||
|
||||
时间复杂度
|
||||
代码中一共两重循环,因此时间复杂度是 $O(n2)$。
|
||||
151
TangDou/AcWing/DP/LIS/LDS的三种求法.md
Normal file
151
TangDou/AcWing/DP/LIS/LDS的三种求法.md
Normal file
@@ -0,0 +1,151 @@
|
||||
### 一、命名规则
|
||||
|
||||
$LIS$:最长上升子序列
|
||||
|
||||
$LDS$:最长下降子序列
|
||||
|
||||
### 二、$LIS$的贪心+二分求法 [~~这个不是重点,就参考对照一下~~]
|
||||
```c++
|
||||
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];
|
||||
}
|
||||
printf("%d\n", fl + 1);
|
||||
```
|
||||
|
||||
### 三、直面$LDS$
|
||||
```c++
|
||||
|
||||
g[0] = a[0];
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (a[i] <= g[gl])
|
||||
g[++gl] = a[i];
|
||||
else
|
||||
*upper_bound(g, g + gl, a[i], greater<int>()) = a[i];
|
||||
}
|
||||
printf("%d\n", gl + 1);
|
||||
|
||||
```
|
||||
### 四、加负号转为$LIS$
|
||||
```c++
|
||||
//最长上升子序列的所有元素全加上负号不就变成最长下降子序列(LDS)
|
||||
memcpy(b, a, sizeof a);
|
||||
|
||||
for (int i = 0; i < n; i++) b[i] = -b[i];
|
||||
g[0] = b[0];
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (b[i] >= g[gl])
|
||||
g[++gl] = b[i];
|
||||
else
|
||||
*upper_bound(g, g + gl, b[i]) = b[i];
|
||||
}
|
||||
printf("%d\n", gl + 1);
|
||||
```
|
||||
|
||||
### 五、数组翻转转为$LIS$
|
||||
```c++
|
||||
//将原数组拷贝出来,翻转,再求LIS就是原数组的LDS
|
||||
memcpy(b, a, sizeof a);
|
||||
reverse(b, b + n);
|
||||
|
||||
g[0] = b[0];
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (b[i] >= g[gl])
|
||||
g[++gl] = b[i];
|
||||
else
|
||||
*upper_bound(g, g + gl, b[i]) = b[i];
|
||||
}
|
||||
printf("%d\n", gl + 1);
|
||||
```
|
||||
|
||||
### 六、实现代码
|
||||
```c++
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
const int N = 1e5 + 10;
|
||||
int a[N], b[N];
|
||||
int f[N], fl, g[N], gl;
|
||||
int n;
|
||||
// LIS:最长上升子序列
|
||||
// LDS:最长下降子序列
|
||||
|
||||
/*
|
||||
测试数据1
|
||||
7
|
||||
3 1 2 1 8 5 6
|
||||
答案:3 3 3
|
||||
|
||||
|
||||
测试数据2
|
||||
8
|
||||
1 2 2 3 4 3 1 0
|
||||
答案:4 4 4
|
||||
*/
|
||||
|
||||
int main() {
|
||||
scanf("%d", &n);
|
||||
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
|
||||
|
||||
//最长上升子序列
|
||||
// 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];
|
||||
// }
|
||||
// printf("%d\n", fl + 1);
|
||||
|
||||
//****************************************************************************************************************//
|
||||
//最长下降子序列:方法1
|
||||
//将原数组拷贝出来,翻转,再求LIS就是原数组的LDS
|
||||
memset(g, 0, sizeof g);
|
||||
gl = 0;
|
||||
|
||||
memcpy(b, a, sizeof a);
|
||||
reverse(b, b + n);
|
||||
|
||||
g[0] = b[0];
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (b[i] >= g[gl])
|
||||
g[++gl] = b[i];
|
||||
else
|
||||
*upper_bound(g, g + gl, b[i]) = b[i];
|
||||
}
|
||||
printf("%d\n", gl + 1);
|
||||
|
||||
//最长下降子序列:方法2
|
||||
//最长上升子序列的所有元素全加上负号不就变成最长下降子序列(LDS)
|
||||
memset(g, 0, sizeof g);
|
||||
gl = 0;
|
||||
memcpy(b, a, sizeof a);
|
||||
|
||||
for (int i = 0; i < n; i++) b[i] = -b[i];
|
||||
g[0] = b[0];
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (b[i] >= g[gl])
|
||||
g[++gl] = b[i];
|
||||
else
|
||||
*upper_bound(g, g + gl, b[i]) = b[i];
|
||||
}
|
||||
printf("%d\n", gl + 1);
|
||||
|
||||
// 最长下降子序列:方法3
|
||||
// 既不改负号,也不翻转,而是正常顺序枚举,如果当前元素小于等于栈顶元素,那么接在栈顶元素后面,否则通过二分找到第一个小于当前元素的栈内元素并替换
|
||||
memset(g, 0, sizeof g);
|
||||
gl = 0;
|
||||
|
||||
g[0] = a[0];
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (a[i] <= g[gl])
|
||||
g[++gl] = a[i];
|
||||
else
|
||||
*upper_bound(g, g + gl, a[i], greater<int>()) = a[i];
|
||||
}
|
||||
printf("%d\n", gl + 1);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
19
TangDou/AcWing/DP/LIS/LIS+LCS练习题.md
Normal file
19
TangDou/AcWing/DP/LIS/LIS+LCS练习题.md
Normal file
@@ -0,0 +1,19 @@
|
||||
https://blog.csdn.net/CUCUC1/article/details/106226577
|
||||
|
||||
## 最长上升子序列$LIS$算法 + 例题
|
||||
|
||||
[最长上升子序列 (LIS) 详解+例题模板 (全)](https://blog.csdn.net/lxt_Lucia/article/details/81206439)
|
||||
|
||||
|
||||
#### 这类题就是要求关于$LIS$具体值,需要用双重循环
|
||||
[HDU1087 Almost Sorted Array](http://acm.hdu.edu.cn/showproblem.php?pid=1087)
|
||||
|
||||
|
||||
#### 二分加贪心优化的$LIS$例题
|
||||
[hdu 1950 Bridging signals](http://acm.hdu.edu.cn/showproblem.php?pid=1950)
|
||||
|
||||
|
||||
https://blog.csdn.net/CUCUC1/article/details/106125668
|
||||
|
||||
#### 单纯的求最大公共子序列的长度
|
||||
[HDU 1159 Common Subsequence](http://acm.hdu.edu.cn/showproblem.php?pid=1159)
|
||||
34
TangDou/AcWing/DP/LIS/POJ1887.cpp
Normal file
34
TangDou/AcWing/DP/LIS/POJ1887.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// http://poj.org/problem?id=1887
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
using namespace std;
|
||||
const int N = 200010;
|
||||
const int INF = 1e8;
|
||||
|
||||
int n;
|
||||
int a[N];
|
||||
int g[N];
|
||||
int kase;
|
||||
|
||||
int main() {
|
||||
while (scanf("%d", &a[1]) == 1 && ~a[1]) {
|
||||
if (kase) puts("");
|
||||
n = 2;
|
||||
|
||||
while (scanf("%d", &a[n]) == 1 && a[n] != -1) n++;
|
||||
n--;
|
||||
|
||||
reverse(a + 1, a + n + 1);
|
||||
for (int i = 1; i <= n; i++) g[i] = INF;
|
||||
|
||||
int ans = 0;
|
||||
for (int i = 1; i <= n; i++) {
|
||||
int k = lower_bound(g + 1, g + n + 1, a[i]) - g;
|
||||
g[k] = a[i];
|
||||
ans = max(ans, k);
|
||||
}
|
||||
printf("Test #%d:\n maximum possible interceptions: %d\n", ++kase, ans);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
20
TangDou/AcWing/DP/LIS/Test.cpp
Normal file
20
TangDou/AcWing/DP/LIS/Test.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
const int N = 1010;
|
||||
int n;
|
||||
int a[N], f[N];
|
||||
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] = 1;
|
||||
for (int j = 1; j < i; j++)
|
||||
if (a[i] > a[j]) f[i] = max(f[i], f[j] + 1);
|
||||
res = max(res, f[i]);
|
||||
}
|
||||
printf("%d", res);
|
||||
return 0;
|
||||
}
|
||||
39
TangDou/AcWing/DP/LIS/lowerBoundTest.cpp
Normal file
39
TangDou/AcWing/DP/LIS/lowerBoundTest.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include <bits/stdc++.h>
|
||||
// 需要仔细研究一下lower_bound,upper_bound,还有手动二分的差别,这个细节很重要
|
||||
// https://www.cnblogs.com/linsinan1995/p/13620590.html
|
||||
using namespace std;
|
||||
const int N = 110;
|
||||
int a[9] = {1, 2, 2, 3, 3, 3, 4, 6, 7};
|
||||
|
||||
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
|
||||
int bsearch_1(int l, int r, int x) {
|
||||
while (l < r) {
|
||||
int mid = (l + r) >> 1;
|
||||
if (a[mid] >= x)
|
||||
r = mid; // check()判断mid是否满足性质
|
||||
else
|
||||
l = mid + 1;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
|
||||
int bsearch_2(int l, int r, int x) {
|
||||
while (l < r) {
|
||||
int mid = (l + r + 1) >> 1;
|
||||
if (a[mid] <= x)
|
||||
l = mid;
|
||||
else
|
||||
r = mid - 1;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
int main() {
|
||||
printf("%d\n", lower_bound(a, a + 9, 3) - a); // 3
|
||||
printf("%d\n", upper_bound(a, a + 9, 3) - a); // 5
|
||||
|
||||
printf("%d\n", bsearch_1(0, 8, 3));
|
||||
printf("%d\n", bsearch_2(0, 8, 3));
|
||||
return 0;
|
||||
}
|
||||
33
TangDou/AcWing/DP/LIS/lowerBoundTest1.cpp
Normal file
33
TangDou/AcWing/DP/LIS/lowerBoundTest1.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
int cmd(int a, int b) {
|
||||
return a > b;
|
||||
}
|
||||
// https://blog.csdn.net/qq_40160605/article/details/80150252
|
||||
/*
|
||||
结果:
|
||||
3 7
|
||||
4 15
|
||||
|
||||
2 7
|
||||
3 4
|
||||
*/
|
||||
int main() {
|
||||
int a[6] = {1, 2, 4, 7, 15, 34};
|
||||
sort(a, a + 6); //按从小到大排序
|
||||
int p1 = lower_bound(a, a + 6, 7) - a; //返回数组中第一个大于或等于被查数的值
|
||||
int p2 = upper_bound(a, a + 6, 7) - a; //返回数组中第一个大于被查数的值
|
||||
cout << p1 << " " << a[p1] << endl;
|
||||
cout << p2 << " " << a[p2] << endl;
|
||||
|
||||
puts("================================================");
|
||||
|
||||
sort(a, a + 6, cmd); //按从大到小排序
|
||||
// int a[6] = {34,15,7,4,2,1};
|
||||
int p3 = lower_bound(a, a + 6, 7, greater<int>()) - a; //返回数组中第一个小于或等于被查数的值
|
||||
int p4 = upper_bound(a, a + 6, 7, greater<int>()) - a; //返回数组中第一个小于被查数的值
|
||||
cout << p3 << " " << a[p3] << endl;
|
||||
cout << p4 << " " << a[p4] << endl;
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user