@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的更多相关文章

  1. # NOI.AC省选赛 第五场T1 子集,与&最大值

    NOI.AC省选赛 第五场T1 A. Mas的童年 题目链接 http://noi.ac/problem/309 思路 0x00 \(n^2\)的暴力挺简单的. ans=max(ans,xor[j-1 ...

  2. NOI.ac #31 MST DP、哈希

    题目传送门:http://noi.ac/problem/31 一道思路好题考虑模拟$Kruskal$的加边方式,然后能够发现非最小生成树边只能在一个已经由边权更小的边连成的连通块中,而树边一定会让两个 ...

  3. NOI.AC NOIP模拟赛 第五场 游记

    NOI.AC NOIP模拟赛 第五场 游记 count 题目大意: 长度为\(n+1(n\le10^5)\)的序列\(A\),其中的每个数都是不大于\(n\)的正整数,且\(n\)以内每个正整数至少出 ...

  4. NOI.AC NOIP模拟赛 第六场 游记

    NOI.AC NOIP模拟赛 第六场 游记 queen 题目大意: 在一个\(n\times n(n\le10^5)\)的棋盘上,放有\(m(m\le10^5)\)个皇后,其中每一个皇后都可以向上.下 ...

  5. NOI.AC NOIP模拟赛 第二场 补记

    NOI.AC NOIP模拟赛 第二场 补记 palindrome 题目大意: 同[CEOI2017]Palindromic Partitions string 同[TC11326]Impossible ...

  6. NOI.AC NOIP模拟赛 第一场 补记

    NOI.AC NOIP模拟赛 第一场 补记 candy 题目大意: 有两个超市,每个超市有\(n(n\le10^5)\)个糖,每个糖\(W\)元.每颗糖有一个愉悦度,其中,第一家商店中的第\(i\)颗 ...

  7. NOI.AC NOIP模拟赛 第四场 补记

    NOI.AC NOIP模拟赛 第四场 补记 子图 题目大意: 一张\(n(n\le5\times10^5)\)个点,\(m(m\le5\times10^5)\)条边的无向图.删去第\(i\)条边需要\ ...

  8. NOI.AC NOIP模拟赛 第三场 补记

    NOI.AC NOIP模拟赛 第三场 补记 列队 题目大意: 给定一个\(n\times m(n,m\le1000)\)的矩阵,每个格子上有一个数\(w_{i,j}\).保证\(w_{i,j}\)互不 ...

  9. NOI.AC WC模拟赛

    4C(容斥) http://noi.ac/contest/56/problem/25 同时交换一行或一列对答案显然没有影响,于是将行列均从大到小排序,每次处理限制相同的一段行列(呈一个L形). 问题变 ...

随机推荐

  1. 关于neo4j的嵌入式和驱动包模式该如何选择,还请解惑

    看了网上的一些资料和Neo4j权威指南这本书.与图遍历相关的介绍都是基于嵌入式模式下的java Api.但是个人觉得在实际的项目中,嵌入式的模式,代码必须放在数据库所在服务器上,且服务器的启停操作都在 ...

  2. 集训队日常训练20180525-DIV1

    A.2805 N*M的图,每次浇水(X1,Y1)-(X2,Y2)围成的矩形,问最后有多少点被浇水了. 暴力. #include<bits/stdc++.h> using namespace ...

  3. for循环取出每个i的值

    <!DOCTYPE html> <html> <head> <title></title> </head> <body&g ...

  4. Javascript实现信息滚动效果的方法

    <html><head><meta http-equiv="Content-Type" content="text/html; charse ...

  5. golang标准库中有些函数只有签名没有函数体是怎么回事?

  6. python 下载安装及运行环境配置(windows)

    第一步:下载python安装包 下载地址:https://www.python.org/downloads/windows/ 我下载的是版本:3.6.4 安装包下载完成后,点击进行安装. 第二步:配置 ...

  7. golang时间与日期相关函数

  8. HZOI20190714 T3建造游乐场

    先放作者的正解: 先说g吧,有i个点的话,在其中i-1个点中有$C_{i-1}^{2}$种边,每个边有选和不选两种情况.如果度不是偶数呢?用剩下那个点给他连上呗.如果剩下那个点度数不是偶数呢?这是不可 ...

  9. Spring_Bean的作用域---和使用外部属性文件

    <!-- 使用 bean的scope属性来配置bean的作用域 singleton:默认值.容器初始时创建bean实例,在整个容器的生命周期内只创建这一个bean单例 prototype:原型的 ...

  10. 使用JS如何消除一个数组里重复的元素

    JS: var arrData = [1,3,5,7,7,8,9,3,10,8,"sdsdsds","sss","ffff","s ...