题意:
给你 n n n 个线段,请你在字典序最小的情况下取不相交的 k k k 个, 且输出方案。
题目分析:
先考虑合适存在有解:
我们贪心地取线段, 尽力取右端点小的,只要最多可能取得的线段超过 k k k 个, 那么显然有解。
接下来考虑字典序最小:
我们依然采用贪心的思路, 我们考虑字典序的定义会造成如下性质:
- 如果一个元素字典序小且加入后符合条件, 我们就应将其加入至最小字典序中。
因此, 我们从前向后遍历每一个元素, 考虑能否加入。
期望复杂度: O ( n 2 log n ) O(n^2 \log n) O(n2logn) 。
显然,从各方面, 都是可以优化的。
- 考虑 n ≤ 1 0 5 n \le 10^5 n≤105 且 L i < R i ≤ 1 0 9 L_i < R_i \le 10^9 Li<Ri≤109, 显然可以考虑进行离散化。
- 我们显然可以预处理一段区间内能取到的最大值。
- 我们考虑加入的时候显然可以根据先前的结果维护。
我们考虑优化2 :
我们发现,我们考虑区间线段数是可以考虑以点为切入点。我们可以沿着线段从左端点尽力往右跳。因此考虑倍增(ST表), 此时, 我们成功的将查询降到了 O ( log n ) O(\log n) O(logn) 级别。
考虑优化3 :
我们不仅可以存一个一定要存储的输出答案(一边解决问题一边输出也可),还可以存储一个答案后的状态。
我们设最大的状态为刚开始读入时的块, 即
[
1
,
l
e
n
]
[1, len]
[1,len](此处
l
e
n
len
len 表示离散化后的长度),后可以用珂朵莉树(又名老司机树, ODT)中分裂的思想, 显然可以用 set
轻松地解决该优化。
具体地,对每一个当前考虑加入的块, 考虑能否加入, 若能加入,进行分裂、加入操作。
由于 set
的底层定义,需要注意大小比较即代码先后顺序的问题。
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, k, p[N << 1], g;
int l[N], r[N], f[N << 1][22];
struct __line {
int l, r;
bool operator < (const __line & x) const {
return r < x.l;
}
};
vector<int> ans;
set<__line> s;
int main() {
cin >> n >> k;
for(int i = 1; i <= n; i++)
cin >> l[i] >> r[i];
for(int i = 1; i <= n; i++)
p[++g] = l[i], \
p[++g] = r[i];
sort(p + 1, p + g + 1);
int len = unique(p + 1, p + g + 1) - p - 1;
// for(int i = 1; i <= len; i++)
// cout << p[i] << "\n";
auto query = [&](int x) { return lower_bound(p + 1, p + len + 1, x) - p; };
for(int i = 1; i <= n; i++)
l[i] = query(l[i]), r[i] = query(r[i]);
for(int i = 1; i <= len + 5; i++)
for(int j = 0; j <= 19; j++)
f[i][j] = (int)2e5 + 1;
for(int i = 1; i <= n; i++)
f[l[i]][0] = min(f[l[i]][0], r[i]);
for(int i = len; i >= 1; i--)
f[i][0] = min(f[i][0], f[i + 1][0]);
for(int j = 1; j <= 19; j++)
for(int i = 1; i <= len; i++)
if(f[i][j - 1] <= len)
f[i][j] = f[f[i][j - 1]][j - 1];
auto find = [&](int l, int r) {
int nowl = l, ret = 0;
for(int i = 19; i >= 0; i--)
if(f[nowl][i] <= r)
nowl = f[nowl][i], ret += (1 << i);
return ret;
} ;
s.insert((__line) {1, len});
int maxn = find(1, len);
// cout << maxn << "\n";
if(maxn < k) { printf("-1"); return 0; }
for(int i = 1; i <= n && ans.size() < k; i++) {
set<__line>::iterator it = s.find((__line) {l[i], r[i]});
// for(__line u : s)
// cout << u.l << " " << u.r << "\n";
// if(it == s.end()) continue;
// cout << i << " " << it -> l << " " << it -> r << "\n";
if(it -> l <= l[i] and r[i] <= it -> r) {
if(maxn - find(it -> l, it -> r) + find(it -> l, l[i]) + find(r[i], it -> r) >= k - ans.size() - 1) {
maxn = maxn - find(it -> l, it -> r) + find(it -> l, l[i]) + find(r[i], it -> r);
ans.push_back(i);
// cout << maxn << "\n";
// cout << it->l << " " << l[i] << " " << r[i] << " " << it->r << "\n";
int p1 = it -> l, p2 = it -> r;
s.erase(it);
s.insert((__line) {p1, l[i]});
s.insert((__line) {r[i], p2});
}
}
}
for(int x : ans) cout << x << "\n";
return 0;
}