题目链接
题解
费用流神题。
对于每一个方格延伸出去的每一根水管,有且仅有一个其他方格延伸出的水管与之相连,这样就不会漏水。即:每根水管的容量为 1 1 ,且必须满流。
然而即使产生了最优情况,整个管网也不一定是一整个联通块,而可能被分成若干块。因此,我们要对每个格点染色,相邻的两个格点,一个连源点,一个连汇点,就可以强制使每两个相邻的方格上都产生流量了。
旋转所花的步数即是费用。将一个方格拆成个点(上下左右中),中间点连上源 or o r 汇点,并根据水管情况向四周连容量 1 1 ,费用的边。四周视作接触点,与对应相邻的另一个接触点连容量 1 1 ,费用的边。接下来,对于每一种形状的格子,分类讨论。
- 射线型:让左、下、右接触点直接连接上接触点。左,右连上去,只转一次,所以费用为 1 1 。下面连上去费用就是。
- 直角型:旋转一次后,发现上下切换 or o r 左右切换。那么上与下、左与右之间各建一条容量为 1 1 ,费用为的边。
- 丁字型:与第一种情况类似,左、上、右接触点直接连接下接触点。左,右连下去,只转一次,所以费用为 1 1 。上面连下去费用就是。
至此,构图已经完成,接下来只要套上模版即可。
代码
//「Luogu4003」Infinity Loop
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
#define c(k) k + (sum << 2)
#define u(k) k + turn * sum
#define r(k) k + ((turn + 1) & 3) * sum
#define d(k) k + ((turn + 2) & 3) * sum
#define l(k) k + ((turn + 3) & 3) * sum
const int maxn = 100005;
const int maxm = 2000005;
const int inf = 1000000000;
bool vis[maxn];
int n, m, cnt, sum, src, snk, ans, dis[maxn];
int tot, ter[maxm], len[maxm], wei[maxm], nxt[maxm], lnk[maxn];
inline void add(int u, int v, int w0, int w1) {
ter[tot] = v;
len[tot] = w0;
wei[tot] = w1;
nxt[tot] = lnk[u];
lnk[u] = tot++;
// printf("(%d, %d): (%d, %d);\n", u, v, w0, w1);
}
void addedge(int u, int v, int w0, int w1, int type) {
if (type) {
swap(u, v);
}
add(u, v, w0, w1);
add(v, u, 0, -w1);
}
bool spfa(int s, int t) {
queue<int> que;
que.push(s);
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0, vis[s] = 1;
int w0, w1;
for (int u, v; !que.empty(); ) {
u = que.front();
que.pop();
vis[u] = 0;
for (int i = lnk[u]; ~i; i = nxt[i]) {
v = ter[i], w0 = len[i], w1 = wei[i];
if (w0 && dis[v] > dis[u] + w1) {
dis[v] = dis[u] + w1;
if (!vis[v]) {
vis[v] = 1;
que.push(v);
}
}
}
}
return dis[t] < inf;
}
int find(int u, int lft) {
if (u == snk) {
return lft;
}
vis[u] = 1;
int w0, w1, tmp, res = 0;
for (int v, i = lnk[u]; ~i && res < lft; i = nxt[i]) {
v = ter[i], w0 = len[i], w1 = wei[i];
if (w0 && !vis[v] && dis[u] + w1 == dis[v]) {
tmp = find(v, min(lft - res, w0));
if (tmp) {
ans += tmp * w1;
len[i] -= tmp;
len[i ^ 1] += tmp;
res += tmp;
}
}
}
vis[u] = 0;
return res;
}
int mcmf() {
int tmp, res = 0;
while (spfa(src, snk)) {
tmp = find(src, inf);
while (tmp) {
res += tmp;
tmp = find(src, inf);
}
}
return res;
}
int main() {
memset(lnk, -1, sizeof(lnk));
scanf("%d %d", &n, &m);
sum = n * m;
src = 0;
snk = sum * 5 + 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int turn = 0, shape;
int type = (i ^ j) & 1;
int id = (i - 1) * m + j;
scanf("%d", &shape);
if (type) {
addedge(src, c(id), inf, 0, 0);
} else {
addedge(c(id), snk, inf, 0, 0);
}
if (i > 1) {
addedge(d(id - m), u(id), 1, 0, type);
}
if (j > 1) {
addedge(r(id - 1), l(id), 1, 0, type);
}
if (shape & 1) {
addedge(u(id), c(id), 1, 0, type);
cnt++;
}
if (shape & 2) {
addedge(r(id), c(id), 1, 0, type);
cnt++;
}
if (shape & 4) {
addedge(d(id), c(id), 1, 0, type);
cnt++;
}
if (shape & 8) {
addedge(l(id), c(id), 1, 0, type);
cnt++;
}
switch (shape) {
case 8:
turn++;
case 4:
turn++;
case 2:
turn++;
case 1:
addedge(r(id), u(id), 1, 1, type);
addedge(d(id), u(id), 1, 2, type);
addedge(l(id), u(id), 1, 1, type);
break;
case 9:
turn++;
case 12:
turn++;
case 6:
turn++;
case 3:
addedge(d(id), u(id), 1, 1, type);
addedge(l(id), r(id), 1, 1, type);
break;
case 13:
turn++;
case 14:
turn++;
case 7:
turn++;
case 11:
addedge(d(id), l(id), 1, 1, type);
addedge(d(id), u(id), 1, 2, type);
addedge(d(id), r(id), 1, 1, type);
break;
}
}
}
cnt >>= 1;
if (mcmf() == cnt) {
printf("%d\n", ans);
} else {
puts("-1");
}
return 0;
}