@noi.ac - 491@ explore
@description@
最近有一个巨大的古代地下遗迹在比特镇被发现。这个地下遗迹的俯视图由 n 行 m 列共 n×m 个格子组成,每个格子表示一个房间,两个房间相邻当且仅当它们存在公共边。经过勘测,这个地下遗迹中有 k 个房间发生了塌陷,这些房间是不可通行的。
为了深入探索这个古代遗迹,考古队需要选择一个没有发生塌陷的房间,从地面上径直打一口井下去,然后探索从该房间能到达的所有房间。
因为打井非常耗费时间和金钱,请写一个程序帮助考古队计算至少需要打多少口井,才能将所有没有发生塌陷的房间都探索一遍。
input
第一行包含三个正整数 n,m,k,分别表示遗迹俯视图的长度、宽度以及塌陷房间的数量。
接下来 k 行,每行两个正整数 xi,yi,表示第 i 个塌陷房间的位置。
output
输出一行一个整数,即需要打的井的数量。
sample input
2 3 2
1 2
2 1
sample output
2
对于 100% 的数据:
1≤xi≤n≤10^9, 1≤yi≤m≤10^9, k<n×m且k≤100000。保证同一个房间最多只会被描述一次。
@solution@
简单来说:一个白色 n*m 棋盘,将其中 k 个格子涂黑,求最终白色四连通块数量。
先离散化,将 n, m 的大小缩小到 10^6 的数量级。
注意离散化时要将一个格子四周的点(常数*3)都要进行离散化,否则会出现原本不相邻的格子离散化后就相邻的情况。
为了方便处理,我们在棋盘的边框外再添加黑格将棋盘包围起来(常数++)。
可以得到一个白色四连通块总是被某个黑色八连通块(注意不是黑色四连通块)包围着。
进一步地,与包围的黑色八连通块相邻的白格决定了这个白色连通块。
所以我们可以只提取与黑格八连通(注意这里也不是四连通)的白格(常数*8)。
(zxb 大佬形象地将其描述为“描边法”)
得到一个粗略的算法:提取一个黑色八连通块,找到与这些黑格八连通的白格,寻找这些白格构成了多少四连通块。
但是有一个小小的 bug:我们提取出的白色四连通块,可能不满足黑连通块包围白连通块,而是反过来白连通块包围黑连通块,这样就会产生重复计数。
但是观察到黑连通块至多只被一个白连通块包围。且如果我们在棋盘外的黑格的外面再添加四连通的白格将棋盘外的黑格包围起来(常数++),每个黑连通块恰好会被一个白连通块包围。
于是就可以用白连通块个数 - 黑连通块个数得到正确答案。
注意找连通块时可能需要 map、lower_bound、hash 等帮助你确定某个棋格是黑格还是白格。
一个 O(nlog n)(log n的瓶颈卡在离散化上)的大常数算法。
@accepted code@
#include<queue>
#include<cstdio>
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define lb lower_bound
typedef pair<int, int> pii;
struct node{
pii p; bool vis;
node(pii _p=mp(0, 0), bool _v=false):p(_p), vis(_v){}
friend bool operator < (node a, node b) {return a.p < b.p;}
friend bool operator == (node a, node b) {return a.p == b.p;}
};
vector<node>b, w;
vector<int>vx, vy;
int main() {
int n, m, k; scanf("%d%d%d", &n, &m, &k);
vx.clear(), vx.pb(0), vx.pb(1), vx.pb(n), vx.pb(n + 1);
vy.clear(), vy.pb(0), vy.pb(1), vy.pb(m), vy.pb(m + 1);
for(int i=1;i<=k;i++) {
int x, y; scanf("%d%d", &x, &y);
b.pb(node(mp(x, y)));
vx.pb(x - 1), vx.pb(x), vx.pb(x + 1);
vy.pb(y - 1), vy.pb(y), vy.pb(y + 1);
}
sort(vx.begin(), vx.end()), vx.erase(unique(vx.begin(), vx.end()), vx.end());
sort(vy.begin(), vy.end()), vy.erase(unique(vy.begin(), vy.end()), vy.end());
for(int i=0;i<k;i++) {
b[i].p.fi = lb(vx.begin(), vx.end(), b[i].p.fi) - vx.begin();
b[i].p.se = lb(vy.begin(), vy.end(), b[i].p.se) - vy.begin();
}
for(int i=1;i<vx.size()-1;i++)
b.pb(node(mp(i, 0))), b.pb(node(mp(i, vy.size()-1)));
for(int i=1;i<vy.size()-1;i++)
b.pb(node(mp(0, i))), b.pb(node(mp(vx.size()-1, i)));
sort(b.begin(), b.end()), b.erase(unique(b.begin(), b.end()), b.end());
int ans = 0;
for(int i=0;i<b.size();i++) {
if( b[i].vis ) continue;
b[i].vis = true; w.clear();
queue<pii>que; que.push(b[i].p);
while( !que.empty() ) {
pii f = que.front(); que.pop();
for(int dx=-1;dx<=1;dx++)
for(int dy=-1;dy<=1;dy++) {
pii p = mp(f.fi + dx, f.se + dy);
int x = lb(b.begin(), b.end(), node(p)) - b.begin();
if( x != b.size() && b[x].p == p ) {
if( !b[x].vis )
b[x].vis = true, que.push(p);
}
else w.pb(p);
}
}
sort(w.begin(), w.end()), w.erase(unique(w.begin(), w.end()), w.end());
for(int j=0;j<w.size();j++) {
if( w[j].vis ) continue;
ans++; w[j].vis = true; que.push(w[j].p);
while( !que.empty() ) {
pii f = que.front(); que.pop();
for(int dx=-1;dx<=1;dx++)
for(int dy=-1;dy<=1;dy++) {
if( dx && dy ) continue;
pii p = mp(f.fi + dx, f.se + dy);
int x = lb(w.begin(), w.end(), node(p)) - w.begin();
if( x != w.size() && w[x].p == p && !w[x].vis )
w[x].vis = true, que.push(p);
}
}
}
ans--;
}
printf("%d\n", ans);
}
@details@
康复计划 - 3。
“我就不信即使我到处都在用大常数 STL,一个 O(nlog n) 的算法跑不过 10^5!”
这是我 TLE 之前的心理活动。
把 map 换成 lower_bound 查找就过了。
人太菜了,写的代码太丑了,评测机只能把大常数 O(nlog n) 当成 O(n^2) 跑。。。
代码非常ACM(指使用define进行缩写)
@noi.ac - 491@ explore的更多相关文章
- # NOI.AC省选赛 第五场T1 子集,与&最大值
NOI.AC省选赛 第五场T1 A. Mas的童年 题目链接 http://noi.ac/problem/309 思路 0x00 \(n^2\)的暴力挺简单的. ans=max(ans,xor[j-1 ...
- NOI.ac #31 MST DP、哈希
题目传送门:http://noi.ac/problem/31 一道思路好题考虑模拟$Kruskal$的加边方式,然后能够发现非最小生成树边只能在一个已经由边权更小的边连成的连通块中,而树边一定会让两个 ...
- NOI.AC NOIP模拟赛 第五场 游记
NOI.AC NOIP模拟赛 第五场 游记 count 题目大意: 长度为\(n+1(n\le10^5)\)的序列\(A\),其中的每个数都是不大于\(n\)的正整数,且\(n\)以内每个正整数至少出 ...
- NOI.AC NOIP模拟赛 第六场 游记
NOI.AC NOIP模拟赛 第六场 游记 queen 题目大意: 在一个\(n\times n(n\le10^5)\)的棋盘上,放有\(m(m\le10^5)\)个皇后,其中每一个皇后都可以向上.下 ...
- NOI.AC NOIP模拟赛 第二场 补记
NOI.AC NOIP模拟赛 第二场 补记 palindrome 题目大意: 同[CEOI2017]Palindromic Partitions string 同[TC11326]Impossible ...
- NOI.AC NOIP模拟赛 第一场 补记
NOI.AC NOIP模拟赛 第一场 补记 candy 题目大意: 有两个超市,每个超市有\(n(n\le10^5)\)个糖,每个糖\(W\)元.每颗糖有一个愉悦度,其中,第一家商店中的第\(i\)颗 ...
- NOI.AC NOIP模拟赛 第四场 补记
NOI.AC NOIP模拟赛 第四场 补记 子图 题目大意: 一张\(n(n\le5\times10^5)\)个点,\(m(m\le5\times10^5)\)条边的无向图.删去第\(i\)条边需要\ ...
- NOI.AC NOIP模拟赛 第三场 补记
NOI.AC NOIP模拟赛 第三场 补记 列队 题目大意: 给定一个\(n\times m(n,m\le1000)\)的矩阵,每个格子上有一个数\(w_{i,j}\).保证\(w_{i,j}\)互不 ...
- NOI.AC WC模拟赛
4C(容斥) http://noi.ac/contest/56/problem/25 同时交换一行或一列对答案显然没有影响,于是将行列均从大到小排序,每次处理限制相同的一段行列(呈一个L形). 问题变 ...
随机推荐
- HDU5583 Kingdom of Black and White
Kingdom of Black and White Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Ja ...
- Leetcode8.String to Integer (atoi)字符串转整数(atoi)
实现 atoi,将字符串转为整数. 该函数首先根据需要丢弃任意多的空格字符,直到找到第一个非空格字符为止.如果第一个非空字符是正号或负号,选取该符号,并将其与后面尽可能多的连续的数字组合起来,这部分字 ...
- Ubuntu 安装 RabbitMQ 和PHP扩展 - CSDN博客
1.ubuntu16.04中安装RabbitMQ 1).首先必须要有Erlang环境支持 安装之前要装一些必要的库: sudo apt-get install build-essential sud ...
- golang之结构体
Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性. Go语言通过用自定义的方式形成新的类型,结构体是类型中带有成员的复合类型. Go 语言中的类型可以被实例化,使用new或&a ...
- 洛谷 1083 (NOIp2012) 借教室——标记永久化线段树 / 差分+二分
题目:https://www.luogu.org/problemnew/show/P1083 听说线段树不标记永久化会T一个点. 注意mn记录的是本层以下.带上标记的min! #include< ...
- 使用Webpack的代码分离实现Vue懒加载
当一个Vue的项目体积变得十分庞大的时候,使用Webpack的代码分离功能将Vue Components,routes或Vuex的代码进行分离并按需加载,会极大的提高App的首屏加载速度. 在Vue的 ...
- spark-ML基础
一.ML组件 ML的标准API使用管道(pipeline)这样的方式,可以将多个算法或者数据处理过程整合到一个管道或者一个流程里运行,其中包含下面几个部分: 1. dataFrame:用于ML的dat ...
- 斯坦福CS课程列表
http://exploredegrees.stanford.edu/coursedescriptions/cs/ CS 101. Introduction to Computing Principl ...
- Nginx - 01 - Nginx初体验
首先下载Nginx,这里电脑太垃圾,没法装虚拟机,所以只能用Windons版本的Nginx. 安装包下载地址:http://nginx.org/en/download.html 下载下来,然后解压: ...
- 权限系统的设计模式 ACL RBAC ABAC
ACL(Access Control List):访问权限列表 如: user1-->AC1 user1-->AC2 user2-->AC1 此时权限汇总成一个列表 这种设计 ...