题目链接
回路限制经典题。
每个点拆成入点和出点,源点连每个点的出点,流量1,费用0,每个点出点连汇点,流量1,费用0,入点和出点之间没有边。
也就是说每个点必须靠其他点流来的流量来流入汇点,同时自己的流量流出去,这时候就会形成环,只要把所有流量流满,就必定是题目要求的情形。
所以每个点向前后左右相邻点连边,如果本来就是这个方向,费用为0,否则费用为1,最小费用即为答案。
#include <cstdio>
#include <queue>
#include <cstring>
#define INF 2147483647
using namespace std;
const int MAXN = 1010;
const int MAXM = 200010;
queue <int> q;
int s, t, now, n, m;
struct Edge{
int from, next, to, rest, cost;
}e[MAXM];
int head[MAXN], num = 1, dis[MAXN], vis[MAXN], Flow[MAXN], pre[MAXN];
inline void Add(int from, int to, int flow, int cost){
e[++num] = (Edge){ from, head[from], to, flow, cost }; head[from] = num;
e[++num] = (Edge){ to, head[to], from, 0, -cost }; head[to] = num;
}
int RoadsExist(){
q.push(s);
memset(dis, 127, sizeof dis);
dis[s] = 0; Flow[s] = INF; pre[t] = 0;
while(!q.empty()){
now = q.front(); q.pop(); vis[now] = 0;
for(int i = head[now]; i; i = e[i].next)
if(e[i].rest && dis[e[i].to] > dis[now] + e[i].cost){
dis[e[i].to] = dis[now] + e[i].cost;
pre[e[i].to] = i;
Flow[e[i].to] = min(Flow[now], e[i].rest);
if(!vis[e[i].to]){
vis[e[i].to] = 1;
q.push(e[i].to);
}
}
}
return pre[t];
}
int a[20][20], mincost, l[] = {233, -1, 1, 0, 0}, r[] = {666, 0, 0, -1, 1};
int id(int i, int j, int k){
return (i - 1) * m + j + k * 500;
}
char ch;
int main(){
scanf("%d%d", &n, &m); s = 999; t = 1000;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j){
ch = getchar(); while(ch == '\n' || ch == '\r' || ch == ' ') ch = getchar();
if(ch == 'U') a[i][j] = 1;
if(ch == 'D') a[i][j] = 2;
if(ch == 'L') a[i][j] = 3;
if(ch == 'R') a[i][j] = 4;
Add(s, id(i, j, 0), 1, 0);
Add(id(i, j, 1), t, 1, 0);
}
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
for(int k = 1; k <= 4; ++k){
int x = i + l[k], y = j + r[k];
if(!x) x = n; if(!y) y = m; if(x > n) x = 1; if(y > m) y = 1;
Add(id(i, j, 0), id(x, y, 1), 1, a[i][j] != k);
}
while(RoadsExist()){
mincost += Flow[t] * dis[t];
for(int i = t; i != s; i = e[pre[i]].from){
e[pre[i]].rest -= Flow[t];
e[pre[i] ^ 1].rest += Flow[t];
}
}
printf("%d\n", mincost);
return 0;
}