@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进行缩写)