题目链接: Too Many Segments (hard version)
大致题意
给定两个正整数n和k, n表示有n个线段(都在OX轴上). 接下来给出n组l, r, 表示每一个线段的左右端点.
对于每一个点, 如果被线段的覆盖次数大于k, 则认为这个点是不好的, 所以我们需要删除一些线段, 使得所有的点都是好的点.
问: 最少删除多少条线段可以使得所有的点都是好的点.
解题思路
很明显, 对于每一个点, 我们都需要知道他被多少条线段所覆盖了, 而每一次给定的都是一个区间.
=> 我们需要做到快速的区间修改, 单点查询. 所以我们采用一个差分数组维护即可.
我们可以从小到大枚举每一个点, 如果当前点被覆盖的线段数目大于了k, 那么我们则需要去删除一些线段. 很容易想到, 我们应该删除能包含当前点的, 且线段要能尽可能覆盖的远的(右端点尽可能大).
这里最初我想的是二分查找, 改了半天, 发现这里其实不适用二分, 因为我们希望右端点尽可能靠右且能包含当前点的点, 无论怎么对线段进行排序, 都不能使得数组满足二分条件. 所以这里我们要采用一个大顶堆维护.
那么堆里的元素如何维护呢? 我们可以把线段按照左端点从小到大排序. 因为我们从小到大枚举每一个点, 所以当我们现在的点等于当前线段的左端点时, 表示我们当前线段可以覆盖到这个点, 则对于后续的点也是有效线段, 我们就把它入堆.
到这里思路就很清晰了, 我们从小到大枚举每一个点, 如果当前点是坏的, 则删除堆顶元素, 直至点变好为止.
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 2E5 + 10;
int b[N]; //差分数组
void change(int l, int r, int c) { b[l] += c, b[r + 1] -= c; }
struct node {
int first, second, id;
bool operator< (const node& t) const {
int l = t.first, r = t.second;
return first < l || (first == l && second < r);
}
};
vector<node> v; //装线段
int main()
{
int n, k; cin >> n >> k;
rep(i, n) {
int l, r; scanf("%d %d", &l, &r);
v.push_back({ l, r, i });
change(l, r, 1);
}
sort(v.begin(), v.end());
int sum = 0, cou = 0;
priority_queue<node> q; //{ r, l, id }
vector<int> res;
int index = 0;
rep(i, N - 5) {
sum += b[i]; //计算当前点的覆盖次数
while (index < v.size() && v[index].first == i) {
int l = v[index].first, r = v[index].second, id = v[index].id;
q.push({ r, l, id }); index++;
}
if (sum <= k) continue;
while (sum > k) {
cou++;
auto op = q.top(); q.pop();
int r = op.first, l = op.second;
change(l, r, -1);
sum--; //本身需要树形结构维护查分数组的, 但是因为我们小到大枚举点, 其实直接把sum--是一样的;
res.push_back(op.id);
}
}
printf("%d\n", cou);
for (int i = 0; i < res.size(); ++i) printf("%d%c", res[i], " \n"[i + 1 == res.size()]);
return 0;
}