P1091 合唱队形 题解
VitrelosTiAFO · · 题解
前言
题解区大多使用的都是时间复杂度
upd(2025.4.5):这篇题解是我远古时期写的,当时只是想介绍一种没什么用的解法,因为一些比较神秘的原因现在是第一篇,所以我打算新增一些内容。
题意分析
题目中要求的“合唱队形”满足的要求其实就是:
做法
这道题想要用
dp 的做法我放在后面了,有需要的朋友可以翻到后面看。
O(n \log n) 求 LIS 长度
在
该组数据中,我们记下以每个数结尾的 LIS 长度,由于上面所讲的,对于长度为
还是上面的道理,我们把还留着的数据的上方的数放到一个数轴上,这样当我们要查询一个数的 LIS 长度时,只需要看这个数处于哪个区间中,它的 LIS 长度就是往前(不包括本身)最靠近它的数所对应的 LIS 长度加一。
那么可以很快找到
实现其实并不困难,按照上述的步骤做就行了。
#include <bits/stdc++.h>
using namespace std;
const int M = 1e5 + 5, INF = 1e9;
int n, a[M];
int f[M], g[M], len;
// f[i] a[i] 为结尾的 LIS 长度
// g[i] 上升子序列长度为 i 时结尾最小值
// len LIS 长度
int main() {
int n;
scanf ("%d", &n);
for (int i = 1; i <= n; i++) scanf ("%d", &a[i]);
for (int i = 1; i <= n; i++) {
int pos = lower_bound(g + 1, g + len + 1, a[i]) - g; //二分查找区间
f[i] = pos;
g[pos] = a[i];//查找到之后还要更新最小值
len = max(len, pos);
}
cout << *max_element(f + 1, f + n + 1); //输出最长的 LIS 长度
return 0;
}
实际上,我们并不需要 f[] 数组,可以直接输出 len。但在本题中需要记录以每个数结尾的 LIS 长度,所以这样写了。
这种做法的时间复杂度是 i 即可。
本题解法
如我们分析的题意一样,本题只需要记录以 f1[] 和 f2[] 来记录这两组数,第 f1[i] + f2[i] - 1(中间有重合所以减去
#include <bits/stdc++.h>
using namespace std;
const int M = 1e5 + 5, INF = 1e9;
int a[M], f1[M], f2[M], g[M], len, ans = -INF;
int main() {
int n;
scanf ("%d", &n);
for (int i = 1; i <= n; i++) scanf ("%d", &a[i]);
len = 0;
for (int i = 1; i <= n; i++) {
int pos = lower_bound(g + 1, g + len + 1, a[i]) - g;
f1[i] = pos;
g[pos] = a[i];
len = max(len, pos);
}
len = 0;
memset(g, 0, sizeof g);
for (int i = n; i >= 1; i--) {
int pos = lower_bound(g + 1, g + len + 1, a[i]) - g;
f2[i] = pos;
g[pos] = a[i];
len = max(len, pos);
}
for (int i = 1; i <= n; i++) ans = max(ans, f1[i] + f2[i] - 1);
cout << n - ans;
return 0;
}
dp 的想法
还是介绍一下比较有用的 dp 吧。
考虑设