This commit is contained in:
HuangHai
2025-08-30 18:35:01 +08:00
commit 1f397eca87
6258 changed files with 1487241 additions and 0 deletions

View 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;
}

View 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)$。

View 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;
}
```

View 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)

View 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;
}

View 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;
}

View 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;
}

View 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;
}