P3356 火星探险问题(最大费用最大流)
题目描述:
给一个矩阵,从左上走到右下,有些点不能走,有些点有石头,每个点可以重复走,
从起始点放置一些机器人,问最多能采到多少个石头.
思路:最大费用最大流
建图:拆点,能走的点可以走无数次,所以我们拆点连边,容量设置为inf,费用为0,然后
点上的石头只能采一次,所以我们再在两个拆点之间连一条容量为1,费用为1的边。
再对其右方和下方的点连边,容量为inf,费用为0,如此跑最大费用流。
然后打印路径,就从起点的发送点开始往下找,判断是否选择就用e[i^1].flow>0?来判断,
即有流量通过,然后每次就把e[i^1].flow--,再进入下一个位置继续打印。
题解代码(我自己的没过,搞不懂...):
#include<cstring> #include<cstdio> #include<algorithm> #include<iostream> #include<queue> using namespace std; typedef pair<int, int> Pair; const int N = 100; const int inf = 0x3f3f3f3f; int car, m, n; int a[N][N], p[N][N]; struct node { int to, nxt, fl, c; }e[N * N * N]; int head[N * N], cnt; queue<int>q; int dis[N * N * 2]; bool vis[N * N * 2]; int pe[N * N * 2], pv[N * N * 2]; void add(int f, int t, int fl, int c) { e[cnt] = (node){ t,head[f],fl,c }; head[f] = cnt++; } void add_edge(int f, int t, int fl, int c) { add(f, t, fl, c); add(t, f, 0, -c); } bool spfa(int S, int T) { for (int i = S; i <= T; ++i) dis[i] = -inf; dis[S] = 0; vis[S] = 1; q.push(S); while (!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; for (int i = head[u]; ~i; i = e[i].nxt) { int v = e[i].to; if (e[i].fl > 0 && dis[v] < dis[u] + e[i].c) { dis[v] = dis[u] + e[i].c; pe[v] = i; pv[v] = u; if (!vis[v]) { vis[v] = 1; q.push(v); } } } } return dis[T] > (-inf); } Pair mcf(int S, int T) { int C = 0, F = 0; int flow = 0; while (spfa(S, T)) { flow = inf; for (int i = T; i != S; i = pv[i]) flow = min(flow, e[pe[i]].fl); C += flow * dis[T]; F += flow; for (int i = T; i != S; i = pv[i]) e[pe[i]].fl -= flow, e[pe[i] ^ 1].fl += flow; } return Pair(F, C); } void DFS(int x, int y, int pos, int k, int T) { int kx, ky, mov; for (int i = head[pos]; ~i; i = e[i].nxt) { int v = e[i].to; if (v == 0) continue; if (v == T) continue; if (v == pos - n * m) continue; if (!e[i ^ 1].fl) continue; e[i ^ 1].fl--;//-1表示这条边已经被选择了一次了 if (v > n * m) {//是拆点兄弟点,进入兄弟 DFS(x, y, v, k, T); return; } if (v == p[x][y] + 1) kx = x, ky = y + 1, mov = 1; else kx = x + 1, ky = y, mov = 0; printf("%d %d\n", k, mov);//打印走的方向 DFS(kx, ky, v + n * m, k, T);//不能写DFS(V) return;//找到任意一个可行方向,就进入该方向的下一个点,退出循环 } } int main() { memset(head, -1, sizeof(head)); scanf("%d%d%d", &car, &m, &n); int num = n * m, now = 0; int S = 0, T = num * 2 + 1; for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) { scanf("%d", &a[i][j]); p[i][j] = ++now; if (a[i][j] == 1) continue;//不能放置 if (a[i][j] == 2) add_edge(p[i][j], p[i][j] + num, 1, 1), add_edge(p[i][j], p[i][j] + num, inf, 0); //如果该位置有石头就需要多连(费用1,代表价值,容量1代表限制次数)边,另一条容量为inf,费用为0表示可以无限走,但是不能获得价值 if (a[i][j] == 0) add_edge(p[i][j], p[i][j] + num, inf, 0); } if (a[1][1] != 1) add_edge(S, 1, car, 0); for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) { if (a[i][j] == 1) continue; //对右方和下方的点连容量为inf,价值为0的边 if (a[i][j + 1] != 1 && p[i][j + 1]) add_edge(p[i][j] + num, p[i][j + 1], inf, 0); if (a[i + 1][j] != 1 && p[i + 1][j]) add_edge(p[i][j] + num, p[i + 1][j], inf, 0); } if (a[n][m] != 1) add_edge(p[n][m] + num, T, car, 0); Pair ans = mcf(S, T);//最大费用流 int f = ans.first; for (int i = 1; i <= f; ++i)//对每一个机器人打印路径 DFS(1, 1, 1, i, T); return 0; }

浙公网安备 33010602011771号