题解 QOJ5048【[ECFinal19K] All Pair Maximum Flow】

news/2024/10/12 8:27:39

题目描述

给你一个 \(n\) 个点 \(m\) 条边的图,它是平面上的正 \(n\) 边形和一些对角线,节点按逆时针方向编号为 \(1\)\(n\)。对角线只可能在节点处相交。每条边有一个容量,求每个点对之间的最大流的和。\(n\leq 200000, m\leq 400000\)

solution

做法

每次找出边权最小的边 \(e\),满足 \(e\)恰好一侧是无界的,删去,在它所在的最小环 \(C\) 的其他边的边权加上 \(C\)。最终当图是一棵树时,可以乱做。

证明大概是证了:

  • 最小割不经过 \(C\) 的边,或者经过 \(e\)\(C\) 的另外一条边。考虑对偶图,把无界区域划分成 \(n\) 份放到树上,具体不知道。

  • 然后通过最小割的性质和构造说明这样改不影响最大流。

平面图对偶图定理

平面图最小割等于对偶图最短路。

实际上这个定理说了和说了一样,含糊不清,我们来点图片:感谢 cjy 老师

如图,左边那个是平面图。将平面图的每一个面(包括有限面和无限面)当成点,两个面之间的边作为边,建立的图叫作对偶图。然后平面图上 \(s\to t\) 的最小割,是按照 \(\vec{s t}\) 这条直线把无限面切开以后两个无限面的代表点的最短路。可以看看第三幅图,虚线切开了无限面。

交了定义之后要证明原命题。观察到,平面图的每一个割都对应一个对偶图路径,最小割 \(\geq\) 最短路;对偶图的每一条路径都对应了平面图的一个割,可以感性理解一下就是这条路径把平面图劈开了,所以最短路 \(\geq\) 最小割。这么一看最小割 \(=\) 最短路就证完了。

性质一

对于一个包含边 \(e\) 的边数最小环 \(C\),其中 \(e\) 的边权 \(\leq\) (当前连通的)多边形中恰有一侧是无限面(注意表述)的边的边权。那么任意两点(假装是 \(s, t\))的最小割要么不经过 \(C\) 的边,要么经过 \(e\)\(C\) 的另外一条边。

证明。在对偶图上考虑。目测一下因为这个环没有把两个无限面包括进去,所以最短路确实只能是经过了零条或者两条 \(C\) 中的边(不能经过一条边吧,出不去;经过三条边也不优)。考虑为什么它一定能切 \(e\)

将多边形的每一个顶点向外引一条射线。\(s\) 引出的射线上方和 \(t\) 引出的射线上方交出的无限面是对偶图的起点,可以随意选择一块区域进入;\(s\) 引出的射线下方和 \(t\) 引出的射线下方交出的无限面是对偶图的终点,可以随意选择一块区域离开。

因为我没有数位板所以直接贺课件的图了,不是商用,希望图没事:

起点是那些黄色点里面选,终点是紫色点里面选,对偶图的边是绿色的,\(C\) 用红色标注。

然后必定经过 \(e\) 就显见了,直接钦定起点跨过 \(e\) 或者终点跨过 \(e\),注意这里的比较对象是蓝边,是说把第一步踩到的蓝边(加上红边)换成 \(e\) 是更优的,即使红边非常短也没有关系。就是右下角的图,原来是 \(x\to y\) 的,你把 \(x\) 搬到 \(v\) 区域内,就能踩到 \(e\) 了。

性质二

删除了多边形上的一条两侧恰好有一侧无界的边 \(e\),并将他的边权加到最小环上,删掉 \(e\),完了以后不影响两个点之间的最大流。

证明。

  1. 原图最小割 \(\geq\) 新图最小割。

    因为如果这个割与 \(C\) 无关,则这个割的大小相同。如果这个割经过 \(e\)\(C\) 的另一条边,则这个割因为改边权大小仍然相同。

  2. 新图最小割 \(\geq\) 原图最小割。

    设新图最小割为 \(E\)。如果 \(E\)\(C\) 无关,则大小相同。否则(注意这个 \(C\) 已经不是环,不能套用性质一),\(E\cup\{e\}\) 是原图的一个割,这个割比 \(E\) 小或者相同。

所以新图最小割 \(=\) 原图最小割。

细节

你发现一旦我们知道树是什么了,可以马上建立 Kruskal 重构树计算答案,因为树上最小割是路径上最小边权,使用这条边作为最小边权的是 Kruskal 重构树上这条边的两个子树跨过去的点对。但是怎么求?

首先就是说找到多边形的最小的“两侧恰有一侧无界”的边 \(e\) 和它所在的最小环 \(C\)。将 \(C\) 上的边边权 \(+e\),然后删去 \(e\)。发现"两侧都无界"的边必然不在其它边的最小环上,也不会被拿出来切掉。所以操作后 \(C\) 中的边会划分"两侧都无界"和“两侧恰有一侧无界”的边,前者加完边权之后就直接被删掉。

如果你真的这样写的话可能会爆炸,不如这样考虑,你建出定理一证明中的对偶图:

  1. 先建出这个对偶图(注意对偶图是一棵树),过程等价于划分多边形区域。随机一个点为一号点,然后随机选方向标号(哦题目标好了是吧),将对角线们的左右两端,强制左 \(<\) 右之后按照左端点降序排序(相同则右端点升序),然后按照顺序拿出一条对角线,将对角线对应的区间的边全部删去并连边后再加入对角线(意思是这个对角线划出了一个新的面,维护一个 map 表示每个位置上的面,每次将那些面拿出来与这次对角线划分出的面连边)。最后 map 还会剩下最后一个跨过 \((1, n)\) 的面也要存下来。
  2. 维护一个 priority_queue 记了边权和边。初始时所有叶子到父亲的边加进去(叶子的父亲明显是它有唯一连边的点)。然后拿一个 \(e\) 出来,它是最小边,在它父亲 \(u\) 那里暴力枚举,除了 \(e\) 之外的边都 \(+e\)。拆掉父亲对应的面之后,这个面就无界了,需要拿父亲的所有边(包括其儿子和父亲)出来枚举,将新的“两侧恰有一侧无界”的边加入优先队列。这里需要维护 \(vis\) 表示每个面是否被拆了(是否无界),初始时所有叶子 \(vis=1\),父亲枚举出边时先将自己 \(vis=1\),然后往 \(vis=0\) 的面插入优先队列(否则一个面会被拆两次)。
  3. 完了以后 Kruskal 重构树启动就完了。事实上 Kruskal 重构树不需要显式地建出来,而直接写并查集算一下就行。

code

#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
template <unsigned umod>
struct modint {/*{{{*/static constexpr int mod = umod;unsigned v;modint() = default;template <class T, enable_if_t<is_integral<T>::value, int> = 0>modint(const T& y) : v(y % mod + (is_signed<T>() && y < 0 ? mod : 0)) {}modint& operator+=(const modint& rhs) { v += rhs.v; if (v >= umod) v -= umod; return *this; }modint& operator-=(const modint& rhs) { v -= rhs.v; if (v >= umod) v += umod; return *this; }modint& operator*=(const modint& rhs) { v = (unsigned)(1ull * v * rhs.v % umod); return *this; }modint& operator/=(const modint& rhs) { assert(rhs.v); return *this *= qpow(rhs, mod - 2); }friend modint operator+(modint lhs, const modint& rhs) { return lhs += rhs; }friend modint operator-(modint lhs, const modint& rhs) { return lhs -= rhs; }friend modint operator*(modint lhs, const modint& rhs) { return lhs *= rhs; }friend modint operator/(modint lhs, const modint& rhs) { return lhs /= rhs; }template <class T> friend modint qpow(modint a, T b) {modint r = 1;for (assert(b >= 0); b; b >>= 1, a *= a) if (b & 1) r *= a;return r;}friend int raw(const modint& self) { return self.v; }friend ostream& operator<<(ostream& os, const modint& self) { return os << raw(self); }explicit operator bool() const { return v != 0; }
};/*}}}*/
using mint = modint<998244353>;
template <int N>
struct dsu {int fa[N + 10], siz[N + 10];dsu() { for (int i = 1; i <= N; i++) fa[i] = i, siz[i] = 1; }int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }bool merge(int x, int y) {x = find(x), y = find(y);if (x == y) return false;if (siz[x] < siz[y]) swap(x, y);siz[x] += siz[y], fa[y] = x;return true;}
};
template <class T>
using p_queue = priority_queue<T, vector<T>, greater<T>>;
struct range {int l, r, id;
};
int n, m, fa[400010], upc[400010];
vector<pair<int, LL>> g[400010];
pair<int, int> edg[400010];
LL nw[400010];
bool vis[400010];
dsu<200010> dsy;
void add(int u, int v, LL w) {if (fa[v] == u) swap(u, v);if (w == -1e18) nw[u] = -1e18;else nw[u] += w;
}
int main() {
#ifndef LOCALcin.tie(nullptr)->sync_with_stdio(false);
#endifcin >> n >> m;vector<range> vec;map<int, int> mp;for (int i = 1, u, v, w; i <= m; i++) {cin >> u >> v >> upc[i];if (u > v) swap(u, v);edg[i] = make_pair(u, v);if (u + 1 == v) mp[u] = i;else if (u == 1 && v == n) mp[n] = i;else vec.push_back({.l = u, .r = v, .id = i});}sort(vec.begin(), vec.end(), [](range lhs, range rhs) { return lhs.l != rhs.l ? lhs.l > rhs.l : lhs.r < rhs.r; });for (auto line : vec) {int p = line.id;auto it = mp.lower_bound(line.l);while (it != mp.end() && it->first < line.r) {fa[it->second] = p;it = mp.erase(it);}mp[line.l] = p;}for (auto it : mp) fa[it.second] = m + 1;for (int i = 1; i <= m; i++) g[fa[i]].emplace_back(i, upc[i]), g[i].emplace_back(fa[i], upc[i]);p_queue<tuple<LL, int, int>> q;for (int i = 1; i <= m + 1; i++) if (g[i].size() == 1) q.emplace(g[i][0].second, i, g[i][0].first), vis[i] = true;while (!q.empty()) {int u, v;LL w;tie(w, u, v) = q.top();q.pop();if (vis[v]) add(u, v, w);else {vis[v] = true;add(u, v, -1e18);for (auto e : g[v]) if (e.first != u) {if (!vis[e.first]) q.emplace(w + e.second, v, e.first);else add(v, e.first, w);}}}vector<tuple<LL, int, int>> t;for (int i = 1; i <= m; i++) if (nw[i] >= 0) {t.emplace_back(nw[i], edg[i].first, edg[i].second);debug("(%d, %d, %lld)\n", edg[i].first, edg[i].second, nw[i]);}sort(t.begin(), t.end(), greater<>{});mint ans = 0;for (auto&& e : t) {int u, v;LL w;tie(w, u, v) = e;// ????????????                 u = dsy.find(u), v = dsy.find(v);ans += mint{w} * dsy.siz[u] * dsy.siz[v];dsy.merge(u, v);}cout << ans << endl;return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/70520.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

南沙C++信奥赛陈老师解一本通题 1939:【07NOIP普及组】纪念品分组

​【题目描述】元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。为使得参加晚会的同学所获得的纪念品价值相对均衡,他要把购来的纪念品根据价格进行分组,但每组最多只能包括两件纪念品, 并且每组纪念品的价格之和不能超过一个给定的整数。为了保证在尽量短的时间内…

SQL注入 浅尝试

情境参加了培训的第七次课, 涉及到了几个信息收集的工具, 这里是第七课的作业题, 及我的解答. (注: 使用本地虚拟机开启dvwa靶场, 10.0.0.155是ubuntu虚拟机的IP, dvwa挂在8080端口上. 登陆后初始化靶场并重新登录. 下列各题都将在靶场不同标签页中练习 复现.)1、在不依赖于DVW…

Invicti v24.10.0 for Windows - Web 应用程序安全测试

Invicti v24.10.0 for Windows - Web 应用程序安全测试Invicti v24.10.0 for Windows - Web 应用程序安全测试 Invicti Standard v24.10.0 – 8 October 2024 请访问原文链接:https://sysin.org/blog/invicti/ 查看最新版。原创作品,转载请保留出处。 作者主页:sysin.orgInv…

Unity3d 切片不起作用的解决办法!

解决办法:查看自己的canvas上的canvas Scaler 的上图参数是否为100. 原因,此处的设置会影响切片的显示,由默认的100改成了0,导致九宫格的失效;

ServiceMesh 3:路由控制(图文总结)

★ ServiceMesh系列 1 Istio部署 1.1 连接测试机 进入测试机服务器... 1.2 安装Istio 1.2.1 通过官方网站下载Istio# 下载最新版本的Istio $ curl -L https://istio.io/downloadIstio | sh -# 或者下载指定版本: $ curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.…

Studio 3T 2024.4 发布下载,新增功能概览

Studio 3T 2024.4 (macOS, Linux, Windows) - MongoDB 的专业 GUI、IDE 和 客户端,支持自然语言查询Studio 3T 2024.4 (macOS, Linux, Windows) - MongoDB 的专业 GUI、IDE 和 客户端,支持自然语言查询 The professional GUI, IDE and client for MongoDB 请访问原文链接:ht…