[COCI2013-2014#6] KRUŽNICE 题解

前言

题目链接:洛谷

题目分析

显然,手模样例发现答案分为以下几个贡献:

  1. 所有圆外面的那个大平面,贡献为 \(1\)
  2. 每个圆至少被分成一部分,贡献为 \(n\)
  3. 如果有一个圆被“拦腰截断了”,即整条直径上都被更小的圆填满了,就额外对答案贡献加 \(1\),这也是我们所求部分。

暴力跳 set

遇事不决,先打暴力;不加优化,不如跳题。一个很显然的想法,如果在处理第 \(i\) 个圆的时候,之前所有比它更小的圆都更新到平面上,那我们只需要看看是不是这个圆的直径被完整覆盖就行了。最暴力的想法就是从左端点开始,一步一步向右边走,看看有没有出现空隙。直接扫是 \(\Theta(n)\) 的,可以用一个 set 来维护当前已经加入的圆,扫描的时候用 lower_bound 来加速。具体细节不用多说,就是模拟。但是有个细节,如果存在一个圆把另一个圆包含了,那么后者可以直接从 set 里删除,因为其不会对之后的答案产生贡献了。

时间复杂度:\(\Theta(n \log n)\),每个点进出 set 一次。

线段树

其实 如果你数据结构学傻了 可以用维护区间被覆盖的最小次数,当查询时,看看这个最小次数 \(cnt\) 如果 \(cnt \geq 1\) 说明被完整覆盖了。区间加,区间查询最小值,当然使用 分块 线段树。另外地,也可以直接用一个 bool 类型的变量来表示这一个区间是不是被完整覆盖了。当然两者都需要先把所有坐标离散化掉。

时间复杂度:\(\Theta(n \log n)\),但逝常数较大 (比分块强),那有没有更优雅一点的做法呢?

并查集

区间上的问题有时可以想象左端点向右端点连边,那我们在插入一个圆的时候,把左端点连向右端点,查询的时候看看当前的左端点是不是已经和右端点连在一起了。因为如果有空隙,两个端点是不会处在同一个连通块里的。

时间复杂度:\(\Theta(n \log n)\),瓶颈在于排序,人家并查集 \(\Theta(n \alpha(n))\) 已经够优秀了,但是能不能把这个并查集的小常数优化掉呢?

单调栈

发现对于某一个圆,如果它被插入进来了,那我们就可以把里面的圆都删了,对答案不会产生影响(是不是就是上面并查集的路径压缩?其实上面可以直接暴力跳 set 的原因就是越过了已经被包含的圆,每个圆最多只被访问一次),发现还和什么很像?单调栈!求矩形面积那题思想是把所有不可能成为解的状态删除,同样在这一题我们也可以及时删除已经被包含进来的圆。具体地,如果先把所有圆按照右端点排序(按照左端点也可以),然后维护一个从栈顶到栈底左端点单调递减的单调栈,那我们能不能在弹栈的时候处理些什么呢?当然可以左右端点一个一个判断过来,但是有没有更优雅的解法呢?发现由于题目性质和单调栈性质,没有圆存在相交或者包含关系,我们只需要统计弹出的圆的直径的和是否等于当前圆的直径就可以了。

时间复杂度:\(\Theta(n \log n)\),瓶颈在于排序,单调栈是 \(\Theta(n)\) 的。

这样,这道题目就被我们解决了。我们一步步优化算法,抽丝剥茧,找到问题的本质。但是实现上的细节,注意此题的离散化,要在点的两边建空点,要不然判断是否填满会出问题。

代码 (略去快读快写)

暴力跳 set 114ms

//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in","r",stdin), freopen(#a".out","w",stdout)
#define main Main(); signed main(){ return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std;

#include <algorithm>
#include <set>

int n;
struct Circle{
	int l, r;
	bool operator < (const Circle & o) const {
		if (r == o.r) return l > o.l;
		return r < o.r;
	}
} cir[300010];

set<pair<int, int> > S;
typedef set<pair<int, int> >::iterator Iter;

bool query(int l, int r){
	Iter it = S.lower_bound({l, -0x3f3f3f3f});
	int now = l;
	while (it != S.end()){
		if (it -> first != now || it -> first > r) return false;
		if (it -> second == r) return true;
		Iter tmp = it; ++tmp;
		now = it -> second, S.erase(it);
		it = tmp;
	}
	return false;
}

void insert(int l, int r){
	Iter it = S.lower_bound({l, 0});
	while (it != S.end()){
		if (it -> first > r) break;
		Iter tmp = it; ++tmp;
		S.erase(it);
		it = tmp;
	}
	S.insert({l, r});
}

signed main(){
	read(n);
	for (int i = 1; i <= n; ++i) {
		int x, r; read(x, r);
		cir[i] = {x - r, x + r};
	}
	sort(cir + 1, cir + n + 1);
	
	int ans = n + 1;
	for (int i = 1; i <= n; ++i){
		if (query(cir[i].l, cir[i].r)) ++ans;
		insert(cir[i].l, cir[i].r);
	}
	
	write(ans);
	return 0;
}

线段树(区间最小值) 275ms

//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in","r",stdin), freopen(#a".out","w",stdout)
#define main Main(); signed main(){ return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std;

#include <algorithm>

int n;

struct Segment{
	int l, r;
	bool operator < (const Segment & o) const {
		return r - l < o.r - o.l;
	}
} seg[300010];
int real[300010 << 1], tot;

struct Segment_Tree{
	#define lson (idx << 1    )
	#define rson (idx << 1 | 1)
	
	struct node{
		int l, r;
		int lazy;
		int minn;
	} tree[300010 << 3];
	
	void build(int idx, int l, int r){
		tree[idx] = {l, r, 0, 0};
		if (l == r) return;
		int mid = (l + r) >> 1;
		build(lson, l, mid), build(rson, mid + 1, r);
	}
	
	void pushtag(int idx, int v){
		tree[idx].lazy += v;
		tree[idx].minn += v;
	}
	
	void pushup(int idx){
		tree[idx].minn = min(tree[lson].minn, tree[rson].minn);
	}
	
	void pushdown(int idx){
		if (tree[idx].lazy == 0) return;
		pushtag(lson, tree[idx].lazy), pushtag(rson, tree[idx].lazy);
		tree[idx].lazy = 0;
	}
	
	void modify(int idx, int l, int r){
		if (tree[idx].r < l || tree[idx].l > r) return;
		if (l <= tree[idx].l && tree[idx].r <= r) return pushtag(idx, 1);
		pushdown(idx), modify(lson, l, r), modify(rson, l, r), pushup(idx);
	}
	
	int query(int idx, int l, int r){
		if (tree[idx].r < l || tree[idx].l > r) return 0x3f3f3f3f;
		if (l <= tree[idx].l && tree[idx].r <= r) return tree[idx].minn;
		return pushdown(idx), min(query(lson, l, r), query(rson, l, r));
	}
	
	#undef lson
	#undef rson
} yzh;

signed main(){
	read(n);
	for (int i=1;i<=n;++i){
		int x, r; read(x, r);
		seg[i] = {x - r, x + r};
		real[++tot] = x - r, real[++tot] = x + r;
	}
	sort(real + 1, real + tot + 1), tot = unique(real + 1, real + tot + 1) - real - 1;
	for (int i=1;i<=n;++i){
		seg[i].l = lower_bound(real + 1, real + tot + 1, seg[i].l) - real;
		seg[i].r = lower_bound(real + 1, real + tot + 1, seg[i].r) - real - 1;
	}
	yzh.build(1, 1, tot);
	
	int ans = n + 1;
	sort(seg + 1, seg + n + 1);
	
	for (int i=1;i<=n;++i){
		if (yzh.query(1, seg[i].l, seg[i].r) > 0) ++ans;
		else yzh.modify(1, seg[i].l, seg[i].r);
	}
	
	write(ans);
	return 0;
}

线段树(区间覆盖) 257ms

//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in","r",stdin), freopen(#a".out","w",stdout)
#define main Main(); signed main(){ return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std;

#include <algorithm>

int n;

struct Segment{
	int l, r;
	bool operator < (const Segment & o) const {
		return r - l < o.r - o.l;
	}
} seg[300010];
int real[300010 << 1], tot;

struct Segment_Tree{
	#define lson (idx << 1    )
	#define rson (idx << 1 | 1)
	
	struct node{
		int l, r;
		bool f;
	} tree[300010 << 3];
	
	void build(int idx, int l, int r){
		tree[idx] = {l, r, false};
		if (l == r) return;
		int mid = (l + r) >> 1;
		build(lson, l, mid), build(rson, mid + 1, r);
	}
	
	void pushtag(int idx){
		tree[idx].f = true;
	}
	
	void pushup(int idx){
		tree[idx].f = tree[lson].f && tree[rson].f;
	}
	
	void pushdown(int idx){
		if (tree[idx].f == false) return;
		pushtag(lson), pushtag(rson);
	}
	
	void modify(int idx, int l, int r){
		if (tree[idx].r < l || tree[idx].l > r) return;
		if (tree[idx].f) return;
		if (l <= tree[idx].l && tree[idx].r <= r) return pushtag(idx);
		pushdown(idx), modify(lson, l, r), modify(rson, l, r), pushup(idx);
	}
	
	bool query(int idx, int l, int r){
		if (tree[idx].r < l || tree[idx].l > r || tree[idx].f) return true;
		if (l <= tree[idx].l && tree[idx].r <= r) return tree[idx].f;
		return pushdown(idx), query(lson, l, r) && query(rson, l, r);
	}
	
	#undef lson
	#undef rson
} yzh;

signed main(){
	read(n);
	for (int i=1;i<=n;++i){
		int x, r; read(x, r);
		seg[i] = {x - r, x + r};
		real[++tot] = x - r, real[++tot] = x + r;
	}
	sort(real + 1, real + tot + 1), tot = unique(real + 1, real + tot + 1) - real - 1;
	for (int i=1;i<=n;++i){
		seg[i].l = lower_bound(real + 1, real + tot + 1, seg[i].l) - real;
		seg[i].r = lower_bound(real + 1, real + tot + 1, seg[i].r) - real - 1;
	}
	yzh.build(1, 1, tot);
	
	int ans = n + 1;
	sort(seg + 1, seg + n + 1);
	
	for (int i=1;i<=n;++i){
		if (yzh.query(1, seg[i].l, seg[i].r) > 0) ++ans;
		else yzh.modify(1, seg[i].l, seg[i].r);
	}
	
	write(ans);
	return 0;
}

并查集 168ms

//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in","r",stdin), freopen(#a".out","w",stdout)
#define main Main(); signed main(){ return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std;

#include <algorithm>

int n;

struct Segment{
	int l, r;
	bool operator < (const Segment & o) const {
		return r - l < o.r - o.l;
	}
} seg[300010];
int real[300010 << 1], tot;

int fa[300010];
int get(int x){
	return fa[x] == x ? x : fa[x] = get(fa[x]);
}

signed main(){
	read(n);
	for (int i=1;i<=n;++i){
		int x, r; read(x, r);
		seg[i] = {x - r, x + r};
		real[++tot] = x - r, real[++tot] = x + r;
	}
	sort(real + 1, real + tot + 1), tot = unique(real + 1, real + tot + 1) - real - 1;
	for (int i=1;i<=n;++i){
		seg[i].l = lower_bound(real + 1, real + tot + 1, seg[i].l) - real;
		seg[i].r = lower_bound(real + 1, real + tot + 1, seg[i].r) - real;
	}
	for (int i=1;i<=tot;++i) fa[i] = i;
	
	int ans = n + 1;
	
	for (int i=1;i<=n;++i){
		int l = get(seg[i].l), r = get(seg[i].r);
		if (l == r) ++ans;
		else fa[l] = r;
	}
	
	write(ans);
	return 0;
}

单调栈 79ms 目前最优解 rank1

#include <algorithm>
#include <cstdio>
using namespace std;

int n;
struct Circle{
	int l, r;
	inline bool operator < (const Circle & o) const {
		return (r == o.r) ? (l > o.l) : (r < o.r);
	}
} cir[300010];

int stack[300010], top;

signed main(){
	fread(buf, 1, MAX, stdin), read(n);
	for (int i = 1, x, r; i <= n; ++i)
		read(x), read(r), cir[i] = {x - r, x + r};
	sort(cir + 1, cir + n + 1);
	int ans = n + 1;
	for (int i = 1; i <= n; ++i){
		int dsum = 0;
		while (top && cir[stack[top]].l >= cir[i].l) dsum += cir[stack[top]].r - cir[stack[top]].l, --top;
		stack[++top] = i, dsum == cir[i].r - cir[i].l && ++ans;
	}
	printf("%d", ans);
	return 0;
}
posted @ 2024-02-22 21:25  XuYueming  阅读(19)  评论(0编辑  收藏  举报