家园 / 星际转移问题(洛谷P2754 分层图+最大流)

本文介绍了如何使用图论和最大流算法解决星际转移问题。通过建立按时间变化的图,并利用并查集判断条件,再枚举时间并跑最大流判断解的存在性。解题过程中提到了汽车加油行驶问题和餐巾计划问题的启发作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

再一次感觉网络流太神奇了qwq

题目链接:[星际转移问题](P4009 汽车加油行驶问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

受到之前那道[汽车加油行驶问题](P4009 汽车加油行驶问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))按汽油剩余量建图的启发,这题自然而然的就想到了按时间建图,但是怎么按时间建图又成了一个难点.

首先判断是否有解,地球与月亮联通时必定有解. 这一步可以用并查集来判断

DSU dsu(n + 5);
for (int i = 1; i <= m; ++ i) {
    for (auto &x : s[i]) cin >> x, ++ x;
    for (int j = 0; j < k - 1; ++ j) dsu.merge(s[i][j], s[i][j + 1]);
}
if (dsu.find(0) != dsu.find(1)) {
    cout << 0 << '\n';
    return 0;
}

由于数据范围很小,我们可以枚举时间,然后跑最大流看是否等于总人数 K K K

本题的所有角色编号

  • 源点: S S S

  • 汇点: T T T

  • 地球: 1 1 1

  • 月球: 0 0 0

  • 空间站: 0 ∽ n + 1 0 \backsim n+1 0n+1 ( ( (默认地球和月球也是空间站 ) ) )

  • 太空船: n + 2 ∽ n + 1 + m n+2 \backsim n+1+m n+2n+1+m

所以除开源汇点外,节点个数 c n t cnt cnt n + m + 2 n+m+2 n+m+2,对于时间 T T T对应的节点编号为 i n d e x + T × ( n + m + 2 ) index+T \times (n+m+2) index+T×(n+m+2)

现在是最重要的建图环节 ! ! ! !!! !!! 设现在枚举到时间 T T T

  • 源点向 0 0 0时刻的地球连边, T T T时刻的月球连边

    d.AddEdge(d.S, 1, k);
    d.AddEdge(0 + cnt * T, d.T, k);
    
  • 任意时刻,人都有三个选择

    1. 太空船 → \rightarrow 空间站

      // j时刻人从空间站到飞船
      d.AddEdge(s[i][now] + j * cnt, i + n + 1 + j * cnt, cap[i]);
      
    2. 空间站 → \rightarrow 太空船

      // j时刻人从飞船到空间站
      d.AddEdge(i + n + 1 + j * cnt, s[i][now] + j * cnt, cap[i]);
      
    3. 停留(这个选择是最容易忽视的)太空船或空间站的部分人数从上一时刻到下一时刻不变

      这一点是受到[餐巾计划问题](P1251 餐巾计划问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))将脏抹布留到明天再洗的启发

      for (int j = 0; j <= T; ++ j) {
          int now = j % s[i].size();
          if (j) {
              // 空间站上的人停留
              for (int p = 0; p <= n + 1; ++ p) {
                  d.AddEdge(p + (j - 1) * cnt, p + j * cnt, INF);
              }
              // 飞船上的人停留
              for (int p = n + 2; p <= n + 1 + m; ++ p) {
                  d.AddEdge(p + (j - 1) * cnt, p + j * cnt, cap[i]);
              }
          }
      }
      

最后判断最大流是否等于 K K K结束枚举

完整代码

#include<bits/stdc++.h>
using namespace std;
using LL = long long;

const int N = 3e4 + 5, INF = 0x3f3f3f3f;
struct Edge {
    int from, to, cap, flow;

    Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}
};

struct Dinic {
    int n, m, S, T;
    vector< Edge > edges;
    vector< int > G[N];
    int d[N], cur[N];
    bool vis[N];

    Dinic(int S, int T) : S(S), T(T) {}
    
    void AddEdge(int from, int to, int cap) {
        edges.push_back(Edge(from, to, cap, 0));
        edges.push_back(Edge(to, from, 0, 0));
        m = edges.size();
        G[from].push_back(m - 2);
        G[to].push_back(m - 1);
    }

    bool BFS() {
        memset(vis, false, sizeof(vis));
        queue<int> Q;
        Q.push(S);
        d[T] = 0;
        vis[S] = true;
        while (!Q.empty()) {
            int x = Q.front();
            Q.pop();
            for (int i = 0; i < G[x].size(); ++ i) {
                Edge& e = edges[G[x][i]];
                if (!vis[e.to] && e.cap > e.flow) {
                    vis[e.to] = true;
                    d[e.to] = d[x] + 1;
                    Q.push(e.to);
                }
            }
        }
        return vis[T];
    }

    int DFS(int x, int a) {
        if (x == T || a == 0) return a;
        int flow = 0, f;
        for (int& i = cur[x]; i < G[x].size(); ++ i) {
            Edge& e = edges[G[x][i]];
            if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow))) > 0) {
                e.flow += f;
                edges[G[x][i] ^ 1].flow -= f;
                flow += f;
                a -= f;
                if (a == 0) break;
            }
        }
        return flow;
    }

    int Maxflow() {
        int flow = 0;
        while (BFS()) {
            memset(cur, 0, sizeof(cur));
            flow += DFS(S, INF);
        }
        return flow;
    }
};
struct DSU {
    vector< int > f;
    DSU(int n) : f(n) {
        iota(f.begin(), f.end(), 0);
    }
    int find(int x) {
        return f[x] == x ? x : f[x] = find(f[x]);
    }
    void merge(int x, int y) {
        x = find(x), y = find(y);
        if (x != y) f[x] = y;
    }
};
int cap[25];
vector< int > s[25];
signed main() {
    cin.tie(nullptr)->sync_with_stdio(false);

    int n, m, k;
    cin >> n >> m >> k;
    DSU dsu(n + 5);
    for (int i = 1; i <= m; ++ i) {
        cin >> cap[i];
        int k;
        cin >> k;
        s[i].resize(k);
        for (auto &x : s[i]) cin >> x, ++ x;
        for (int j = 0; j < k - 1; ++ j) dsu.merge(s[i][j], s[i][j + 1]);
    }
    if (dsu.find(0) != dsu.find(1)) {
        cout << 0 << '\n';
        return 0;
    }
    for (int T = 1; ; ++ T) {
        // 0 moon
        // 1 earth
        // 0~n+1 space station
        // n+2~n+1+m space ship
        int cnt = n + m + 2;
        Dinic d(cnt * (T + 1) + 1, cnt * (T + 1) + 2);
        d.AddEdge(d.S, 1, k);
        d.AddEdge(0 + cnt * T, d.T, k);
        for (int i = 1; i <= m; ++ i) {
            for (int j = 0; j <= T; ++ j) {
                int now = j % s[i].size();
                if (j) {
                    // 空间站上的人停留
                    for (int p = 0; p <= n + 1; ++ p) {
                        d.AddEdge(p + (j - 1) * cnt, p + j * cnt, INF);
                    }
                    // 飞船上的人停留
                    for (int p = n + 2; p <= n + 1 + m; ++ p) {
                        d.AddEdge(p + (j - 1) * cnt, p + j * cnt, cap[i]);
                    }
                }
                // 人从空间站到飞船
                d.AddEdge(s[i][now] + j * cnt, i + n + 1 + j * cnt, cap[i]);
                // 人从飞船到空间站
                d.AddEdge(i + n + 1 + j * cnt, s[i][now] + j * cnt, cap[i]);
            }
        }
        if (d.Maxflow() == k) {
            cout << T << '\n';
            return 0;
        }
    }
    return 0;
}

总算是把网络流24题肝完了2333

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值