【Codeforces 1181E】A Story of One Country (Easy & Hard)(分治 & set)
Description
在一个二维平面上有若干个矩形。定义一个矩形的(或有边在无限远处)区域为符合条件的条件为:
- 这个区域仅包含一个矩形,且不能使边界穿过任何一个矩形的内部。
- 这个区域可以用一个水平或竖直的直线分割为两个符合条件的区域。
现给定一个有 \(n\) 个矩形的平面,请你判断整个平面区域是否符合条件。
Hint
- for all: \(1\le n\le 10^5\)
- for Easy: \(n\le 10^3\)
- \(0\le \text{坐标大小} \le 10^9\)
Solution
Easy Version
对于小规模的数据,我们直接按题意分治即可。
对于一组矩形,我们可以先找一条分割线,分为两组矩形然后递归处理。
如何找分割线?不难想到按 \(x,y\) 坐标分别直接排序。
这个做法复杂度 \(O(n^2 \log n)\),足以通过 Easy 数据。
Code(Easy)
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : Codeforces 1181E1 A Story of One Country (Easy)
*/
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e5 + 5;
struct area {
int u, d, l, r;
} ar[N];
typedef vector<area> areaList;
int n;
bool cmp_x(const area& x, const area& y) {
return x.l < y.l;
}
bool cmp_y(const area& x, const area& y) {
return x.u < y.u;
}
bool solve(areaList ar) {
if (ar.size() == 1) return true;
sort(ar.begin(), ar.end(), cmp_x);
int maxR = ar.begin()->r;
for (register int i = 1; i < ar.size(); i++)
if (maxR <= ar[i].l)
return solve(areaList(ar.begin(), ar.begin() + i))
&& solve(areaList(ar.begin() + i, ar.end()));
else maxR = max(maxR, ar[i].r);
sort(ar.begin(), ar.end(), cmp_y);
int maxD = ar.begin()->d;
for (register int i = 1; i < ar.size(); i++)
if (maxD <= ar[i].u)
return solve(areaList(ar.begin(), ar.begin() + i))
&& solve(areaList(ar.begin() + i, ar.end()));
else maxD = max(maxD, ar[i].d);
return false;
}
signed main() {
cin >> n;
areaList dat;
for (register int i = 1; i <= n; i++) {
area ar;
cin >> ar.l >> ar.u >> ar.r >> ar.d;
dat.push_back(ar);
}
if (solve(dat)) cout << "YES" << endl;
else cout << "NO" << endl;
}
Hard
上面的算法之所以效率不高,很大原因是由于递归内部的排序。
因此不妨直接维护矩形的有序——set。
这里维护两个 set,分别按 \(x, y\) 坐标排序。
假如找到了一条分割线,那么我们将这部分的矩形导出转移至两个新的 set 中,递归分治处理。
但假如分割线在非常后面,这就导致导出矩形的时间开销非常大。
于是我们使用 启发式分裂 的思想:当分割线在非常后面,虽然前面元素非常多,但后面元素却很少。使用为什么不导出后面,保留前面呢?
假如我们总是这样做,那么导出部分的效率就可以得到保证。
现在又有一个棘手的问题——怎么找分割线?
假如直接扫 set 找,最坏还得扫过整个 set,复杂度又退化到了平方级别。
于是我们有想到 Non-boring sequences 中的 中途相遇法。
那个是一维意义上的中途相遇,而这里则是 二维平面上的中途相遇。
具体怎么做?题目不是给我们每个矩形的四个参数吗?于是我们根据 4 个方向,一个开 4 个 set。
并且我们要求 4 个方向同时向中间推进。
当其中一个方向找到了分割线,那么就 直接开始导出。由于这里可以导出的矩形个数一定不超过整个 set 的一半,于是不需要判断两边的个数多少,自然就保证了启发式分裂的实施。
时间复杂度为 \(T(n) = T(x) + T(n - x) + O(x\log n)\) ,其中 \(x\) 为导出的矩形的个数。
当 \(x = \frac{n}{2}\) 时达到最劣情况,为 \(O(n\log^2 n)\)。
Code(Hard)
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : Codeforces 1181E2 A Story of One Country (Hard)
*/
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
using namespace std;
const int N = 1e5 + 5;
const int MaxU = 1e9;
struct area {
int l, r, u, d;
};
typedef vector<area> areaList;
int n;
struct cmp_l {
inline bool operator () (const area& a, const area& b) {
return a.l != b.l ? a.l < b.l : a.u < b.u;
}
};
struct cmp_r {
inline bool operator () (const area& a, const area& b) {
return a.r != b.r ? a.r > b.r : a.d > b.d;
}
};
struct cmp_u {
inline bool operator () (const area& a, const area& b) {
return a.u != b.u ? a.u < b.u : a.l < b.l;
}
};
struct cmp_d {
inline bool operator () (const area& a, const area& b) {
return a.d != b.d ? a.d > b.d : a.r > b.r;
}
};
typedef set<area, cmp_l> setL;
typedef set<area, cmp_r> setR;
typedef set<area, cmp_u> setU;
typedef set<area, cmp_d> setD;
bool solve(setL& sl, setR& sr, setU& su, setD& sd) {
int size = sl.size();
if (size == 1) return true;
setL pl; setR pr;
setU pu; setD pd;
setL::iterator itl = sl.begin();
setR::iterator itr = sr.begin();
setU::iterator itu = su.begin();
setD::iterator itd = sd.begin();
int maxR = itl->r, maxD = itu->d;
int minL = itr->l, minU = itd->u;
while (--size) {
++itl;
if (maxR <= itl->l) {
for (setL::iterator j = sl.begin(); j != itl; j++)
pl.insert(*j), pr.insert(*j), pu.insert(*j), pd.insert(*j);
for (setL::iterator j = sl.begin(); j != itl; j++)
sr.erase(*j), su.erase(*j), sd.erase(*j);
sl.erase(sl.begin(), itl);
return solve(pl, pr, pu, pd) && solve(sl, sr, su, sd);
} else {
maxR = max(maxR, itl->r);
}
++itr;
if (minL >= itr->r) {
for (setR::iterator j = sr.begin(); j != itr; j++)
pl.insert(*j), pr.insert(*j), pu.insert(*j), pd.insert(*j);
for (setR::iterator j = sr.begin(); j != itr; j++)
sl.erase(*j), su.erase(*j), sd.erase(*j);
sr.erase(sr.begin(), itr);
return solve(pl, pr, pu, pd) && solve(sl, sr, su, sd);
} else {
minL = min(minL, itr->l);
}
++itu;
if (maxD <= itu->u) {
for (setU::iterator j = su.begin(); j != itu; j++)
pl.insert(*j), pr.insert(*j), pu.insert(*j), pd.insert(*j);
for (setU::iterator j = su.begin(); j != itu; j++)
sl.erase(*j), sr.erase(*j), sd.erase(*j);
su.erase(su.begin(), itu);
return solve(pl, pr, pu, pd) && solve(sl, sr, su, sd);
} else {
maxD = max(maxD, itu->d);
}
++itd;
if (minU >= itd->d) {
for (setD::iterator j = sd.begin(); j != itd; j++)
pl.insert(*j), pr.insert(*j), pu.insert(*j), pd.insert(*j);
for (setD::iterator j = sd.begin(); j != itd; j++)
sl.erase(*j), sr.erase(*j), su.erase(*j);
sd.erase(sd.begin(), itd);
return solve(pl, pr, pu, pd) && solve(sl, sr, su, sd);
} else {
minU = min(minU, itd->u);
}
}
return false;
}
signed main() {
cin >> n;
areaList dat;
for (register int i = 1; i <= n; i++) {
area ar;
cin >> ar.l >> ar.u >> ar.r >> ar.d;
dat.push_back(ar);
}
sort(dat.begin(), dat.begin(), cmp_l());
setL sl(dat.begin(), dat.end());
sort(dat.begin(), dat.begin(), cmp_r());
setR sr(dat.begin(), dat.end());
sort(dat.begin(), dat.begin(), cmp_u());
setU su(dat.begin(), dat.end());
sort(dat.begin(), dat.begin(), cmp_d());
setD sd(dat.begin(), dat.end());
if (solve(sl, sr, su, sd)) cout << "YES" << endl;
else cout << "NO" << endl;
}
【Codeforces 1181E】A Story of One Country (Easy & Hard)(分治 & set)的更多相关文章
- Codeforces Round #622(Div 2) C1. Skyscrapers (easy version)
题目链接: C1. Skyscrapers (easy version) 题目描述: 有一行数,使得整个序列满足 先递增在递减(或者只递增,或者只递减) ,每个位置上的数可以改变,但是最大不能超过原来 ...
- Codeforces - 1195D1 - Submarine in the Rybinsk Sea (easy edition) - 水题
https://codeforc.es/contest/1195/problem/D1 给\(n\)个等长的十进制数串,定义操作\(f(x,y)\)的结果是"从\(y\)的末尾开始一个一个交 ...
- Codeforces Round #622 (Div. 2) C1. Skyscrapers (easy version)(简单版本暴力)
This is an easier version of the problem. In this version n≤1000n≤1000 The outskirts of the capital ...
- Codeforces Global Round 7 D1. Prefix-Suffix Palindrome (Easy version)(字符串)
题意: 取一字符串不相交的前缀和后缀(可为空)构成最长回文串. 思路: 先从两边取对称的前后缀,之后再取余下字符串较长的回文前缀或后缀. #include <bits/stdc++.h> ...
- Codeforces Round #570 (Div. 3) E. Subsequences (easy version) (搜索,STL)
题意:有一长度为\(n\)的字符串,要求得到\(k\)不同的它的子序列(可以是空串),每个子序列有\(|n|-|t|\)的贡献,求合法情况下的最小贡献. 题解:直接撸个爆搜找出所有子序列然后放到set ...
- Codeforces Round #602 Div2 D1. Optimal Subsequences (Easy Version)
题意:给你一个数组a,询问m次,每次返回长度为k的和最大的子序列(要求字典序最小)的pos位置上的数字. 题解:和最大的子序列很简单,排个序就行,但是题目要求字典序最小,那我们在刚开始的时候先记录每个 ...
- Codeforces Round #355 (Div. 2) D. Vanya and Treasure 分治暴力
D. Vanya and Treasure 题目连接: http://www.codeforces.com/contest/677/problem/D Description Vanya is in ...
- Codeforces Round #256 (Div. 2) C. Painting Fence(分治贪心)
题目链接:http://codeforces.com/problemset/problem/448/C C. Painting Fence time limit per test 1 second m ...
- Codeforces 161D Distance in Tree(树的点分治)
题目大概是,给一棵树,统计距离为k的点对数. 不会DP啊..点分治的思路比较直观,啪啪啪敲完然后AC了.具体来说是这样的: 树上任何两点的路径都可以看成是一条过某棵子树根的路径,即任何一条路径都可以由 ...
随机推荐
- socket套接字(字节序、地址转换)
什么是socket: socket可以看成是用户进程与内核网络协议栈的编程接口. socket不仅可以用于本机的进程间通信,还可以用于网络上 不同主机之间的进程通信.IPv4套接口地址结构 struc ...
- Android状态栏与布局重叠解决方案
问题起因: 同组的同事将项目全局设置成了沉浸式,对于我这个半路过来开发的人 可真是头疼呵~ 没办法,那就我自己添加一个头吧.也可以在布局中取消沉浸式,不过我这个是在fragment中,为了不修改之前的 ...
- 源码分析:Semaphore之信号量
简介 Semaphore 又名计数信号量,从概念上来讲,信号量初始并维护一定数量的许可证,使用之前先要先获得一个许可,用完之后再释放一个许可.信号量通常用于限制线程的数量来控制访问某些资源,从而达到单 ...
- [LeetCode题解]234. 回文链表 | 快慢指针 + 反转链表
解题思路 找到后半部分链表,再反转.然后与前半部分链表比较 代码 /** * Definition for singly-linked list. * public class ListNode { ...
- 4.Spring Boot web开发
1.创建一个web模块 (1).创建SpringBoot应用,选中我们需要的模块: (2).SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来 (3).自己编 ...
- html页面转PDF、图片操作记录
前言 日常开发中,我们有可能会碰到从系统中导出数据并打印的需要,打印的格式是常规的表格形式,例如: 本文记录使用js库html2canvas + jspdf实现html转PDF.图片,并下载 画出页面 ...
- 【MathType教学】表示分类的大括号怎么打
大括号是一种常见的数学符号,可以用于集合.分段函数中,其实大括号还可以用来总结数学知识,比如对三角形进行分类,此时用的大括号可以称为表示分类的大括号.MathType作为专业的数学公式编辑器,可以快速 ...
- SD卡被格式化了还能恢复吗
SD卡轻便小巧,它的主要功能是拓展便携式设备.包括:数据相机.手机及其他的多媒体播放器等的存储空间,缓解设备本身的存储压力. 很多用户反应,SD卡使用了一定的时间后,会出现SD卡受损的提示,再次打开的 ...
- Vim注释行的方法
目录 一.Visual block 加注释 去注释 二.正则表达式 加注释 去注释 一.Visual block 加注释 1.首先按键盘上的ESC进入命令行模式 2.再按Ctrl+V进入VISUAL ...
- python接口测试2-开发WEB接口
首先要安装flask包: pip install flask 开发一个简单的API接口 # 1. 导入包 from flask import Flask, request # 2. 实例化一个 app ...