P4553 80人环游世界 题解

· · 题解

Description

对于每个人的路线,可以在任意位置开始,任意位置结束,但途经节点编号单调递增。两个城市间移动有固定费用,也有可能不连通。保证有解,求最小花费。 # Solution 考虑不同路径,任意起点终点,人流量一定,和费用最小,可以确定是费用流模型。注意具体的,因为点的出入人流量一定,所以考虑**有源汇上下界最小费用可行流**。 为了保证每个点贡献唯一,将每个点拆成入点和出点,从入点向出点连一条边,表示经过该城市。像这样,流量从入点进入,全部途经向出点的连边,保证贡献唯一。为保证人流流量一定,对于一个点拆成的两点,从出点到汇点的连边是一条上下界均为 $V_i$ 费用为 $0$ 的边。 虚拟源汇点,表示有 $m$ 个人旅游。因为旅游人数一定,所以所以新建立两点,将源汇点也拆成两点,入点到出点的连边是一条上下界均为 $m$ 费用为 $0$ 的边。并从源点的出点向每个城市的入点连下界为 $0$ 上界为 $+\infty$ 费用为 $0$ 的边。汇点同理。 对于一条从城市 $i$ 到城市 $j$ 费用为 $cost$ 的边,从 $i$ 的出点向 $j$ 的入点连一条下界为 $0$ 上界为 $+\infty$ 费用为 $cost$ 的边。表示经过点 $i$ 流向点 $j$。 ## 有源汇上下界最小费用可行流 如果不会上下界网络流看[这道题](https://www.luogu.com.cn/problem/P5192)。 将最大流改成费用流也是一个道理,边的费用不变,先将边的下界流满,注意预先累加费用。 再虚拟超级源汇点,将流量不平衡的边与超级源汇点对应连边,意为根据每条边剩余可调整流量将流量调整平衡。 上述两步费用累加起来,就是可行流最小费用。 ## C++ Code ```cpp #include <bits/stdc++.h> using namespace std; class Dinic { public: typedef int TYPE; static const int maxn = 1e4, inf = 0x7f7f7f7f, maxm = inf; class edge { public: int to, rev; TYPE flow, cost; edge(int to, TYPE flow, TYPE cost, int rev): to(to), flow(flow), cost(cost), rev(rev) {} edge() {} }; std::vector<edge>vec[maxn]; TYPE MinCost, MaxFlow, dist[maxn], W[maxn]; int cur[maxn], n, m, s, t; bool vis[maxn]; Dinic(int n, int m, int s, int t): n(n), m(m), s(s), t(t), MinCost(0), MaxFlow(0), cur({0}), dist({0}), vis({false}) {} Dinic() {} inline void _Add_Edge_(int from, int to, TYPE flow, TYPE cost) { vec[from].push_back(edge(to, flow, cost, vec[to].size())); vec[to].push_back(edge(from, 0, -cost, vec[from].size() - 1)); } inline void Add_Edge(int from, int to, TYPE flowL, TYPE flowR, TYPE cost) { W[from] -= flowL, W[to] += flowL; MinCost += flowL * cost; _Add_Edge_(from, to, flowR - flowL, cost); } inline bool SPFA() { std::memset(dist, inf, sizeof(dist)); dist[t] = 0; std::memset(cur, 0, sizeof(cur)); std::queue<int>que; for (que.push(t), vis[t] = true; !((bool) que.empty());) { int u = que.front(); vis[u] = false; que.pop(); for (edge &e : vec[u]) { int v = e.to; if (vec[v][e.rev].flow && dist[u] + vec[v][e.rev].cost < dist[v]) { dist[v] = dist[u] + vec[v][e.rev].cost; if (!vis[v]) { que.push(v); vis[v] = true; } } } } return dist[s] != inf; } inline TYPE dfs(int u, TYPE flow) { if (u == t || !flow) { return flow; } vis[u] = true; TYPE res = 0; int sz = vec[u].size(); for (int &i = cur[u]; i < sz && flow; ++i) { edge &e = vec[u][i]; int v = e.to; if (e.flow && dist[u] == dist[v] + e.cost && !vis[v]) { TYPE Preflow = dfs(v, std::min(flow, e.flow)); res += Preflow, flow -= Preflow, MinCost += Preflow * (e.cost); e.flow -= Preflow, vec[v][e.rev].flow += Preflow; } } vis[u] = false; return res; } inline TYPE Get_MaxFlow_MinCost() { MaxFlow = 0; while (SPFA()) { std::memset(vis, false, sizeof(vis)); TYPE Preflow = dfs(s, inf); MaxFlow += Preflow; } return MaxFlow; } inline TYPE clac(int S, int T) { Add_Edge(T, S, 0, inf, 0); int PreS = n + 1, PreT = n + 2; this->s = PreS, this->t = PreT; TYPE sum = 0; for (int i = 0; i <= n; ++i) { if (W[i] > 0) { Add_Edge(PreS, i, 0, W[i], 0); sum += W[i]; } else if (W[i] < 0) { Add_Edge(i, PreT, 0, -W[i], 0); } } if (Get_MaxFlow_MinCost() < sum) { return -1; } else { return (MaxFlow = vec[S].back().flow); } } inline void Solve() { #define id(a,CLASS) (a+(CLASS*N)) int N, M; scanf("%d%d", &N, &M); this[0] = Dinic(N * 2 + 5, inf, N * 2 + 1, N * 2 + 2); for (int i = 1; i <= N; ++i) { int tmp; scanf("%d", &tmp); Add_Edge(id(i, 0), id(i, 1), tmp, tmp, 0); } for (int i = 1; i < N; ++i) { for (int j = i + 1; j <= N; ++j) { int tmp; scanf("%d", &tmp); if (tmp != -1) { Add_Edge(id(i, 1), id(j, 0), 0, inf, tmp); } } } int LimS = N * 2 + 3, LimT = N * 2 + 4; Add_Edge(s, LimS, M, M, 0); Add_Edge(LimT, t, M, M, 0); for (int i = 1; i <= N; ++i) { Add_Edge(LimS, id(i, 0), 0, inf, 0); Add_Edge(id(i, 1), LimT, 0, inf, 0); } TYPE Ans = clac(s, t); if (~Ans) { printf("%d\n", MinCost); } else { printf("No Solution.%d\n", MinCost); } } }; Dinic Main; int main() { Main.Solve(); return 0; } ```