下面是一道安徽大学校赛题的二分图最小权匹配。
显然,对于寻找最小权,我们只要将所有边权取负,再找对大权,此时的最大权的绝对值就是原图的二分图最小权。
此外,根据本题题意,所有棋子都要落到边上,可转化为二分图匹配问题,所有棋子对应左部点,而所有边上的格子对应为右部点,最终棋盘整理好即为所有棋子都要有对应的格子,即一个完美匹配,所以本题是一道二分图最小权值完美匹配问题。
不过本题转化为二分图匹配问题有一个前提:在考虑棋子移动时,我们无需考虑格子上已有的棋子,也就是说我们的棋子在移动时可穿过其他棋子,因为其他棋子也可发生移动。也就是说,我们甚至无需考虑棋子的移动,只需要考虑棋子与有效格子的匹配,这样原问题就变成了一个二分图匹配问题。
当格子数量小于棋子数量时,可特判为无解,这样场上的棋子与格子数最多不会超过,对于用 bfs 实现的 km 算法,时间复杂度为 ,其中M为格子数。
本题的另一特点是要处理好边权,而边权也很明显,即为每个棋子到格子的曼哈顿距离。
//牛客,整理棋盘,二分图
#include<bits/stdc++.h>
#include<queue>
using namespace std;
typedef long long ll;
const int N = 405;
const int M = 805;
const int INF = 0x3f3f3f3f;
int n , m;
char c[N][N];
int a[M][M];
int posx[M], posy[M], pre[M];
int lx[M], ly[M], matchx[M], matchy[M];
int slack[M], d;
bool visx[M], visy[M];
queue<int> q;
int nl, nr;
void aug(int v) {
while(v) {
int t = matchx[pre[v]];
matchx[pre[v]] = v;
matchy[v] = pre[v];
v = t;
}
}
void bfs(int s) {
for (int i = 1; i <= nr; i++) slack[i] = INF;
memset(visx, 0, sizeof visx);
memset(visy, 0, sizeof visy);
memset(pre, 0, sizeof pre);
while(!q.empty()) q.pop();
q.push(s);
while(1) {
while(!q.empty()) {
int u = q.front();
q.pop();
visx[u] = 1;
for (int v = 1; v <= nr; v++) {
if (!visy[v]) {
if (lx[u] + ly[v] - a[u][v] < slack[v]) {
slack[v] = lx[u] + ly[v] - a[u][v];
pre[v] = u;
}
if (!slack[v]) {
visy[v] = 1;
if (!matchy[v]) {
aug(v);
return;
}else q.push(matchy[v]);
}
}
}
}
d = INF;
for (int i = 1; i <= nr; i++) {
if (!visy[i]) d = min(d, slack[i]);
}
if (d == INF) break;
for (int i = 1; i <= nl; i++) if (visx[i]) lx[i] -= d;
for (int i = 1; i <= nr; i++) if (visy[i]) ly[i] += d;
else slack[i] -= d;
for (int i = 1 ; i <= nr; i++) {
if (!visy[i] && !slack[i]) {
visy[i] = 1;
if (!matchy[i]) {
aug(i);
return;
}else q.push(matchy[i]);
}
}
}
}
void km() {
for (int i = 1; i <= nl; i++) {
for (int j = 1; j <= nr; j++) {
lx[i] = max(lx[i], a[i][j]);
}
}
for (int i = 1; i <= nl; i++) {
bfs(i);
}
}
void init() {
for (int i = 0 ; i <= 800; i++) {
posx[i] = -1;
posy[i] = -1;
matchy[i] = matchx[i] = 0;
lx[i] = 0;
ly[i] = 0;
}
}
int main() {
int T;
cin >> T;
while (T--) {
init();
cin >> n >> m;
int sp = n * 4 - 4;
nl = m;
nr = sp;
int cnt = 1;
for (int i = 1 ; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> c[i][j];
if (c[i][j] == '#') {
posx[cnt++] = (i-1) * n + j; // 记录棋子位置
}
a[i][j] = -INF;
}
}
if (m > sp) {
puts("-1");
continue;
}
int cnty = 0;
for (int i = 1; i <= n; i++) {
posy[++cnty] = i;
}
for (int i = 2; i <= n-1; i++) {
posy[++cnty] = (i - 1) * n + 1;
posy[++cnty] = (i - 1) * n + n;
}
for (int i = 1; i <= n; i++) {
posy[++cnty] = (n - 1) * n + i;
}// 记录有效格子位置
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= cnty; j++) {
int x1 = (posx[i] - 1) / n + 1, x2 = (posy[j] - 1) / n + 1;
int y1 = (posx[i] - 1) % n + 1, y2 = (posy[j] - 1) % n + 1;
int w = abs(x1 - x2) + abs(y1 - y2);//处理边权
// cout << x1 << " " << y1 << " , " << x2 << " " << y2 << ": " << w << endl;
a[i][j] = -1 * w;//边权取负
}
}
km();
int ans = 0;
for (int i = 1; i <= nl; i++) ans += lx[i];
for (int i = 1; i <= nr; i++) ans += ly[i];
cout << abs(ans) << endl;
}
return 0;
}