1 条题解
-
0
自动搬运
来自洛谷,原作者为

STDquantum
尝试用唯物的角度去理解,瞬间就是永恒。搬运于
2025-08-24 22:24:20,当前版本为作者最后更新于2020-10-19 22:19:05,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
原题链接:P6822 [PA2012] Tax。
虽然说是套路题,但是如果是第一次做的话也还是有些困难的,于是我就来用图形形象化地说明一下。只是便于理解,并没有正解的推导过程。
直接说做法(连边均为有向边):
-
建立超级源点 和超级汇点 ,并且无向边拆成有向边、化边为点。
-
对于所有从 出发的边,从 向这些边对应的点连权值为原图权值的边;对于所有结束于 的边,从这些边对应的点向 连权值为原图权值的边。
-
所有边向自己的反向边连权值为原图权值的边。
-
将一个点出发的所有边按照边权排序,对于排序后相邻的两条边 与 ,从 向 连权值为 的边,从 向 连权值为 的边。
到此连边过程就结束了,从 跑单源最短路(不能用 SPFA!!!), 即为题中所求的值。
现在举一个简单的例子:

边上方的数字代表从左到右的有向边的编号,边下方的数字代表从右到左的有向边的编号。
按照所给方式连边后的图(节点编号对应上图边的编号):

现在我们来看一条合法的从 到 的路径:$s\rightarrow 1\rightarrow 5\rightarrow3\rightarrow t$。
为防止语义冲突,在原图中的边定义为 , 与 均为原图中节点编号。
- 的边权为 边权,代表从 出来时的代价。
- 中,前者边权为 边权,后者边权为 的边权差距。此处说为差距是因为不清楚两者之间的大小关系,若 大于 ,那么 的权值为 ,合到一块 的权值就为 的边权;若 小于 ,那么 的权值为 ,合到一块 的权值就为 的边权。
- 的边权为 边权,代表到 结束的代价。
引入不同边的差值可以让我们直接计算出最大边权,从而避免了讨论的情况。
新图中的点分为了三个集合,源汇点、正向边和反向边。
在新图中可以发现如下性质:
- 当走源汇点和正向边之间的边时,所计算的是离开 或来到 的代价;
- 当走正向边和反向边内部的边时(其实就是一个点出来的边),代表着反悔操作,也就是换了一条边走。
- 当在正向和反向边之间横跳时(如 ),代表又走过了一个节点,并计算了最大边的权值。
就这样。
C++98(去掉了C++11的特性和一些可能会让人迷惑的地方):码风有一(
亿)点毒瘤,可以拿来对拍。云剪贴板。
C++11版本:
#include <bits/stdc++.h> using namespace std; namespace Sweet { template <typename T> inline void read(T &x) { char ch; int f = 1; while (ch = getchar(), ch > '9' || ch < '0') if (ch == '-') f = -1; x = (ch ^ 48); while (ch = getchar(), ch >= '0' && ch <= '9') x = x * 10 + (ch ^ 48); x *= f; } template <typename T> inline void write(T x) { static int stk[100], top = 0; if (x == 0) return (void)putchar('0'); if (x < 0) x = -x, putchar('-'); while (x) stk[++top] = x % 10, x /= 10; while (top) putchar(stk[top--] + '0'); } typedef long long ll; const int N = 1e5 + 10, V = 4e5 + 10; basic_string<pair<int, int> > e[V]; inline void add(int x, int y, int z) { e[x] += {y, z}; } // +=相当于push_back struct T { int x; ll dis; T(int X, ll Dis) : x(X), dis(Dis) {} bool operator<(const T &rhs) const { return dis > rhs.dis; } }; extern int s, t; ll dis[V]; ll Dijkstra() { memset(dis, 0x3f, sizeof(dis)); priority_queue<T> q; q.emplace(s, dis[s] = 0); static T u(0, 0); while (!q.empty()) { u = q.top(), q.pop(); if (u.dis != dis[u.x]) continue; for (auto v : e[u.x]) { if (dis[v.first] > u.dis + v.second) { q.emplace(v.first, dis[v.first] = u.dis + v.second); } } } return dis[t]; } struct Edge { int v, w, id; bool operator<(const Edge &rhs) const { return w < rhs.w; } }; basic_string<Edge> g[N]; // 似乎比vector要快一点? basic_string<Edge>::iterator it; int n, m, s = 0, t = 1; inline void main() { read(n), read(m); for (int i = 1, a, b, c; i <= m; ++i) { read(a), read(b), read(c); g[a] += {b, c, i << 1}, g[b] += {a, c, i << 1 | 1}; // 编号方式:异或1得到反向边 } for (int i = 1; i <= n; ++i) { if (g[i].empty()) continue; // 后面迭代器的写法要求不能为空,直接用下标遍历不用判 sort(g[i].begin(), g[i].end()); for (auto j : g[i]) { add(j.id ^ 1, j.id, j.w); (i == 1) && (add(s, j.id, j.w), 0); // 短路表达式 = if (j.v == n) && (add(j.id, t, j.w), 0); } for (it = g[i].begin(), ++it; it != g[i].end(); ++it) { add(it->id, (it - 1)->id, 0); add((it - 1)->id, it->id, it->w - (it - 1)->w); } } write(Dijkstra()); } } // namespace Sweet int main() { Sweet::main(); return 0; } -
- 1
信息
- ID
- 5887
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者