[CEOI2002] Royal guards

题目描述

从前有一个王国,这个王国的城堡是 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;
}
  • 24
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值