P7562 [JOISC 2021 Day4] イベント巡り 2 (Event Hopping 2) 题解

题意:

给你 n n n 个线段,请你在字典序最小的情况下取不相交的 k k k 个, 且输出方案。

题目分析:

先考虑合适存在有解:

我们贪心地取线段, 尽力取右端点小的,只要最多可能取得的线段超过 k k k 个, 那么显然有解。

接下来考虑字典序最小:

我们依然采用贪心的思路, 我们考虑字典序的定义会造成如下性质:

  • 如果一个元素字典序小且加入后符合条件, 我们就应将其加入至最小字典序中。

因此, 我们从前向后遍历每一个元素, 考虑能否加入。

期望复杂度: O ( n 2 log ⁡ n ) O(n^2 \log n) O(n2logn)

显然,从各方面, 都是可以优化的。

  1. 考虑 n ≤ 1 0 5 n \le 10^5 n105 L i < R i ≤ 1 0 9 L_i < R_i \le 10^9 Li<Ri109, 显然可以考虑进行离散化。
  2. 我们显然可以预处理一段区间内能取到的最大值。
  3. 我们考虑加入的时候显然可以根据先前的结果维护。

我们考虑优化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;
}
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值