模板 - 动态规划 - 最长上升子序列
原来B序列并不是最长上升子序列,虽然他确实是上升的,但是顺序不一定对(指它不一定是原序列的一个子序列)。
最长上升子序列
模板。
int LIS() {
int len = 1;
B[1] = A[1];
for(int i = 2; i <= n; ++i) {
if(A[i] > B[len]) {
B[++len] = A[i];
} else {
int pos = lower_bound(B + 1, B + 1 + len, A[i]) - B;
B[pos] = A[i];
}
}
return len;
}
最长不下降子序列
把上面的大于换成大于等于,把大于等于(lower_bound())换成大于(upper_bound())。
int LIS() {
int len = 1;
B[1] = A[1];
for(int i = 2; i <= n; ++i) {
if(A[i] >= B[len]) {
B[++len] = A[i];
} else {
int pos = upper_bound(B + 1, B + 1 + len, A[i]) - B;
B[pos] = A[i];
}
}
return len;
}
构造
例题
CF - 1296E2
题目链接https://codeforces.com/contest/1296/problem/E2
这里要求最长下降子序列的长度,并划分为若干个不下降子序列。于是把数字全部取反,转化成求最长上升子序列的长度,并划分为若干个不上升的子序列。
考虑 \(B[x]\) 的意思是当前字符串长度为 \(x\) 的上升子序列的最优结尾字符,在lower_bound()找到 \(pos\) 之后就说明 \(A[i]\) 这个字符是可以替换掉 \(B[pos]\) 原本的字符的,所以在进行链划分的时候可以直接接在这个字符的后面!
int n;
char s[200005];
int A[200005];
int B[200005];
int id[200005];
int C[200005];
int LIS() {
int len = 1;
B[1] = A[1];
C[1] = 1;
id[1] = 1;
for(int i = 2; i <= n; ++i) {
if(A[i] > B[len]) {
B[++len] = A[i];
C[i] = len;
id[len] = i;
} else {
int pos = lower_bound(B + 1, B + 1 + len, A[i]) - B;
B[pos] = A[i];
C[i] = C[id[pos]];
id[pos] = i;
}
}
return len;
}
void test_case() {
scanf("%d%s", &n, s + 1);
for(int i = 1; i <= n; ++i)
A[i] = 'z' + 1 - s[i];
int len = LIS();
printf("%d\n", len);
for(int i = 1; i <= n; ++i)
printf("%d%c", C[i], " \n"[i == n]);
}
从这里可以得到启发,每次找到一个位置之后都往接在原来的后面,在得到最长上升子序列的同时就会得到一个不下降子序列的链划分。且把这些链的末端连起来应该就是最长上升子序列。
参考资料
https://blog.csdn.net/lxt_Lucia/article/details/81206439
https://www.cnblogs.com/yuelian/p/8745807.html