题目描述
从前有一个王国,这个王国的城堡是 mm 行 nn 列的一个矩形,被分为 m \times nm×n 个方格。一些方格是墙,而另一些是空地。这个王国的国王在城堡里设了一些陷阱,每个陷阱占据一块空地。
一天,国王决定在城堡里布置守卫,他希望安排尽量多的守卫。
守卫们都是经过严格训练的,所以一旦他们发现同行或同列中有人的话,他们立即向那人射击。因此,国王希望能够合理地布置守卫,使他们互相之间不能看见,这样他们就不可能互相射击了。守卫们只能被布置在空地上,不能被布置在陷阱或墙上,且一块空地只能布置一个守卫。如果两个守卫在同一行或同一列,并且他们之间没有墙的话,他们就能互相看见。(守卫就像象棋里的车一样)
你的任务是写一个程序,根据给定的城堡,计算最多可布置多少个守卫,并设计出布置的方案。
输入格式
第一行有两个整数 mm 和 nn,表示城堡的规模。
第 22 到第 (m + 1)(m+1) 行,每行 nn 个整数,第 (i +1)(i+1) 行第 jj 列的数 a_{i, j}ai,j 表示城堡第 ii 行第 jj 列的方格的信息,其中 00 表示空地,11 表示陷阱,22 表示墙。
输出格式
本题存在 Special Judge。
首先输出一行一个整数 kk,表示最多可布置的守卫个数。
然后输出 kk 行,每行两个整数 x, yx,y,表示在第 xx 行第 jj 列放一个守卫。
输入输出样例
输入 #1复制
3 4 2 0 0 0 2 2 2 1 0 1 0 2
输出 #1复制
2 1 2 3 3
说明/提示
样例输入输出 1 解释
如图(黑色方格为墙,白色方格为空地,圆圈为陷阱,G表示守卫)
数据规模与约定
对于全部的测试点,保证 1 \leq m, n \leq 2001≤m,n≤200,0 \leq a_{i, j} \leq 20≤ai,j≤2。
三倍经验紫题,你真的不来做吗? 1263 2825 6062
题意差不多都是这样的:一个网格图,有的地方是墙;有的地方是空地,可以放哨兵(或其他的);有的地方是陷阱,不可以放哨兵。一个哨兵可以看到他所在的行和列(看不穿墙)。要求哨兵之间不可相互看到,求最多放多少个哨兵?
首先显然地,所有空地一定被看到,因为如果有空地没被看到就可以再放一个哨兵,这也是为什么这道题的结论到 P6062 也是正确的。
考虑连续的一横段或一竖段最多只有一个人,这是经典的二分图匹配模型。陷阱不放人,空地对应的行向列连一条边,然后跑二分图匹配。
现在考虑墙的限制。一道横的墙会把上面的一段和下面的一段隔开,上面的列和下面的列就互不影响了。怎么办呢,以这道墙的边的编号为新的列,如图:
连边时,连新的编号就行了,这样可以处理墙的限制,列也是同理的
代码实现上,如果有一道墙,他的上面应当被作为列的编号,对应的是墙上面所有可达的空地。右面省略。另外,为处理不被墙限制的点,在地图的外围围一堵墙。由于 nn 只有区区 200200,可以让点 (i,j)(i,j) 的下方和右方的线标号为 (i-1)\times200+j(i−1)×200+j 和 (i-1)\times200+j+40000(i−1)×200+j+40000 这样可以不重叠,然后跑一遍二分图,最后最大流就是结果。
另外要构造出一组可行解,由行连向列的正向边如果有流通过,对应的点是答案。
我在代码里用了 dinic,由于有 n^2n2 个点,复杂度是 n^2\times \sqrt {n^2}=O(n^3)n2×n2=O(n3) 的。
#include<bits/stdc++.h>
using namespace std; int wl[202][202][2], now[80002];
int n, m, s, t, mp[202][202], dep[80002], res; const int inf = 1e9;
struct edge{int u, v, w, nxt;}e[1000010]; int cnt = 1, head[80002];
inline void add(int u, int v){
e[++cnt] = (edge){u, v, 1, head[u]}; head[u] = cnt;
e[++cnt] = (edge){v, u, 0, head[v]}; head[v] = cnt;
} // 加边
inline bool bfs(){
memset(dep, -1, sizeof dep);
queue<int>q;
q.emplace(s); dep[s] = 0;
while(q.size()){ int x = q.front(); q.pop();
for(int i = head[x]; i; i = e[i].nxt){ auto o = e[i]; int v = o.v;
if(o.w && dep[v] == -1) dep[v] = dep[x] + 1, q.emplace(v);}
} for(int i = 0; i <= 80001; i++) now[i] = head[i]; return dep[t] != -1;
}
inline int dfs(int x, int lim){
if(x == t) return lim; int used = 0;
for(int &i = now[x]; i; i = e[i].nxt){
auto o = e[i]; int v = o.v;
if(dep[v] == dep[x] + 1 && o.w){
int t = dfs(v, min(lim, o.w));
used += t; e[i].w -= t; e[i^1].w += t;
if (t == lim){now[x] = i; break;}
}
}
if (!used){dep[x] = -1;}
return used;
}//网络流部分
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(0);std::cout.tie(0);
cin >> n >> m; s = 0, t = 80001;
for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) cin >> mp[i][j];
for(int i = 0; i <= n + 1; i++) mp[i][0] = 2, mp[i][m + 1] = 2;
for(int i = 1; i <= m + 1; i++) mp[0][i] = 2, mp[n + 1][i] = 2; //先围一堵墙
for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++)
if(mp[i][j] != 2){
int ps = (i - 1) * 200 + j;
if(mp[i + 1][j] == 2){
add(s, ps); for(int k = i; mp[k][j] != 2; k--) wl[k][j][0] = ps;
}if(mp[i][j + 1] == 2){ ps += 40000;
add(ps, t); for(int k = j; mp[i][k] != 2; k--) wl[i][k][1] = ps;
} //这部分是求出每个点对应的行和列(新的)
} int ct = cnt + 1;
for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++)
if(!mp[i][j]) add(wl[i][j][0], wl[i][j][1]); //二分图的连边
while(bfs()) res += dfs(s, inf); //跑最大流
cout << res << '\n';
for(int i = ct; i <= cnt; i++) if(!e[i].w && (e[i].v >= 40001))
cout << (e[i].v - 40001) / 200 + 1 << ' ' << (e[i].u - 1) % 200 + 1 << '\n'; //构造方案。
return 0;
}