Antenna Placement(二分图匹配)
oj: hduoj 3020
VJudge
题意
有一个 n × m n\times m n×m 的网格,网格内有若干个城市,每个基站可以覆盖两个相邻得城市。请你使用尽可能少的基站覆盖所有的城市。
题解
先把相邻的城市之间连边建立二分图,然后求最小路径覆盖。
先给出一些概念:
- 匹配:给定一个二分图,在 G G G 的一个子图 G ′ G' G′ 中,如果 G ′ G' G′ 的边集中的任意两条边都不依附于同一个顶点,则称 G ′ G' G′ 的边集为 G G G 的一个匹配。
- 最大匹配:在所有的匹配中,边数最多的那个匹配就是二分图的最大匹配了。
- 顶点覆盖:在顶点集合中,选取一部分顶点,这些顶点能够把所有的边都覆盖了。这些点就是顶点覆盖集。
- 最小顶点覆盖:在所有的顶点覆盖集中,顶点数最小的那个叫最小顶点集合。
- 独立集:在所有的顶点中选取一些顶点,这些顶点两两之间没有连线,这些点就叫独立集。
- 最大独立集:在所有的独立集中,顶点数最多的那个集合。
- 路径覆盖:在图中找一些路径,这些路径覆盖图中所有的顶点,每个顶点都只与一条路径相关联。
- 最小路径覆盖:在所有的路径覆盖中,路径个数最小的就是最小路径覆盖了。
下面给出一些图的性质:
- 最大团 = 补图的最大独立集
- 最小边覆盖 = 二分图最大独立集 = ∣ V ∣ |V| ∣V∣ - 最小路径覆盖
- 最小路径覆盖 = ∣ V ∣ |V| ∣V∣ - 最大匹配数
- 最小顶点覆盖 = 最大匹配数
- 最小顶点覆盖 + 最大独立数 = ∣ V ∣ |V| ∣V∣
- 最小割 = 最小点权覆盖集 = 点权和 - 最大点权独立集
以上概念及性质参考自kitten.one的博客
代码
#pragma GCC optimize(2)
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <ctime>
#include <stack>
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(int i = (a), lennn = (b); i <= lennn; ++i)
#define outval(a) cout << "Debuging...|" << #a << ": " << a << "\n"
using namespace std;
typedef long long LL;
const int maxn = 1605;
const int inf = 0x3f3f3f3f;
inline int read() {
int x(0), f(1); char ch(getchar());
while (ch<'0' || ch>'9') {
if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') {
x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
int n, m;
char s[45][15];
/*
二分图匹配-HK算法
初始化:清空邻接表
参数:
Maxmatch:返回最大匹配数
ln:左边点集的大小
注意:点的编号从0开始
*/
vector<int> G[maxn];
int Mx[maxn], My[maxn]; //Mx存与左侧集合相连接的右边的点,My存与右边的
int dx[maxn], dy[maxn]; //层次编号
int dis; //标识是否找到增广路及找到的增广路的深度
int used[maxn]; //dfs中标识是否走过
bool bfs(int ln) {
queue<int> q;
dis = inf;
memset(dx, -1, sizeof(dx));
memset(dy, -1, sizeof(dy));
_for(i, ln) {
if (Mx[i] == -1) {
//如果没有匹配过
q.push(i);
dx[i] = 0;
}
}
while (q.size()) {
int u = q.front();
q.pop();
if (dx[u] > dis) break; //如果已经找到增广路
int sz = G[u].size();
_for(i, sz) {
int v = G[u][i];
if (dy[v] == -1) {
dy[v] = dx[u] + 1;
if (My[v] == -1) dis = dy[v];
else {
dx[My[v]] = dy[v] + 1;
q.push(My[v]);
}
}
}
}
return dis != inf;
}//找出增广路并保存路径
bool dfs(int u) {
int sz = G[u].size();
_for(i, sz) {
int v = G[u][i];
if (!used[v] && dy[v] == dx[u] + 1) {
used[v] = 1;
if (My[v] != -1 && dy[v] == dis) continue;
if (My[v] == -1 || dfs(My[v])) {
My[v] = u;
Mx[u] = v;
return 1;
}
}
}
return 0;
}
int Maxmatch(int ln) {
int ans = 0;
memset(Mx, -1, sizeof(Mx));
memset(My, -1, sizeof(My));
while (bfs(ln)) {
memset(used, 0, sizeof(used));
_for(i, ln) {
if (Mx[i] == -1 && dfs(i)) {
ans++;
}
}
}
return ans;
}
void init() {
_for(i, n * m) G[i].clear();
}
void sol() {
init();
_for(i, n) scanf("%s", s[i]);
int d[4][2] = {
0, 1, 1, 0, 0, -1, -1, 0};
int cnt = 0;
_for(i, n) _for(j, m) if(s[i][j] == '*') {
++cnt;
_for(k, 4) {
int x = i + d[k][0], y = j + d[k][1];
if(x >= 0 && x < n && y >= 0 && y < m && s[x][y] == '*') {
G[i * m + j].push_back(x * m + y);
}
}
}
printf("%d\n", cnt - Maxmatch(n * m) / 2);
}
int main() {
int T = read();
_for(i, T) {
n = read(), m = read();
sol();
}
return 0;
}