前言

题目链接:洛谷

题目分析

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

  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;
}

[COCI2013-2014#6] KRUŽNICE 题解的更多相关文章

  1. ZOJ Monthly, June 2014 月赛BCDEFGH题题解

    比赛链接:点击打开链接 上来先搞了f.c,,然后发现状态不正确,一下午都是脑洞大开,, 无脑wa,无脑ce...一样的错犯2次.. 硬着头皮搞了几发,最后20分钟码了一下G,不知道为什么把1直接当成不 ...

  2. Noip 2014酱油记+简要题解

    好吧,day2T1把d默认为1也是醉了,现在只能期待数据弱然后怒卡一等线吧QAQ Day0 第一次下午出发啊真是不错,才2小时左右就到了233,在车上把sao和fate补掉就到了= = 然后到宾馆之后 ...

  3. Clash Credenz 2014 Wild Card Round题解

    A题 简单模拟. /************************************************************************* > File Name: ...

  4. P6739 [BalticOI 2014 Day1] Three Friends 题解

    目录 写在前面 Solution 何为字符串哈希(可跳过): Code 写在前面 P6739 [BalticOI 2014 Day1] Three Friends 听说这题可以用比较暴力的做法过,比如 ...

  5. 2014上海全国邀请赛题解 HDOJ 5090-5099

    HDOJ 5090 水题.从小到大排序,能够填充达到符合条件的.先填充好.填充之后进行调整. 传送门:pid=5090">点击打开链接 #include <cstdio> ...

  6. 2014 百度之星题解 1002 - Disk Schedule

    Problem Description 有非常多从磁盘读取数据的需求,包含顺序读取.随机读取.为了提高效率,须要人为安排磁盘读取.然而,在现实中,这样的做法非常复杂.我们考虑一个相对简单的场景. 磁盘 ...

  7. 2014 北京邀请赛ABDHJ题解

    A. A Matrix 点击打开链接 构造,结论是从第一行開始往下产生一条曲线,使得这条区间最长且从上到下递减, #include <cstdio> #include <cstrin ...

  8. CHD 2014迎新杯比赛题解

    A. 草滩的魔法学校 分析: 高精度乘法 或 JAVA大数类 很明显 10000 的阶乘已经远远超过 64 位数能表示的范围了.所以我们要用一个比较大的数组来存放这个数.那数组要开多少位合适呢?我们不 ...

  9. 2014 百度之星 题解 1004 Labyrinth

    Problem Description 度度熊是一仅仅喜欢探险的熊,一次偶然落进了一个m*n矩阵的迷宫,该迷宫仅仅能从矩阵左上角第一个方格開始走,仅仅有走到右上角的第一个格子才算走出迷宫,每一次仅仅能 ...

  10. NOIP 2014 提高组 题解

    NOIP 2014 提高组 题解 No 1. 生活大爆炸版石头剪刀布 http://www.luogu.org/problem/show?pid=1328 这是道大水题,我都在想怎么会有人错了,没算法 ...

随机推荐

  1. oracle数据库与oracle实例

    1 oracle数据库分类 1.1 单租户数据库 ORACLE12C之前的oracle数据库都是单租户数据库.单租户数据库是独立和完整的数据库,包括ORACLE的元数据和应用的数据. 1.2 容器数据 ...

  2. SpringBoot的Security和OAuth2的使用

    创建项目 先创建一个spring项目. 然后编写pom文件如下,引入spring-boot-starter-security,我这里使用的spring boot是2.4.2,这里使用使用spring- ...

  3. python重拾基础第三天

    本节内容 函数基本语法及特性 参数与局部变量 返回值 嵌套函数 递归 匿名函数 函数式编程介绍 高阶函数 内置函数 1. 函数基本语法及特性 背景提要 现在老板让你写一个监控程序,监控服务器的系统状况 ...

  4. 开源日志组件Sejil--附带日志管理界面

    1.开源日志组件源码:  https://github.com/alaatm/Sejil 2.下载下来发现里面对于不同的.net core 版本的配置提供了对应的示例 .Net Core 3.1 Pr ...

  5. python基础-数据容器的通用操作

    五种数据容器的特性   列表list[]  元组tuple()  字符串str""   集合set{}   字典dict{key:value} 元素数量 支持多个 支持多个 支持多 ...

  6. Docker部署php运行环境

    编写docker-compose.yml配置文件,使用nginx作为web服务器,转发php的请求. version: "3" services: web: image: ngin ...

  7. AOP面向切面编程@Aspect 注解用法

    AOP简介 AOP为Aspect Oriented Programming 的缩写,意为"面向切面编程",通过预编译方式和运行预期动态代理实现程序功能的统一维护的一种技术.AOP是 ...

  8. 怎么判断一个变量arr的话是否为数组(此题用 typeof 不行)?

    arr instanceof Array arr.constructor == Array Object.protype.toString.call(arr) == '[Object Array]'

  9. 动手学深度学习——CNN应用demo

    CNN应用demo CNN实现简单的手写数字识别 import torch import torch.nn.functional as F from torchvision import datase ...

  10. Java异步判断线程池所有任务是否执行完成的方法

    1.使用ExecutorService和CountDownLatch的方法示例 在Java中,当我们使用线程池(如ExecutorService)来执行异步任务时,常常需要知道所有任务是否都已经完成. ...