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;
}

 

posted @ 2021-04-14 21:19  cono奇犽哒  阅读(83)  评论(0)    收藏  举报