CF903G Yet Another Maxflow Problem 题解
注意到最大流不好处理,而众所周知最大流等于最小割,因此考虑求出最小割。
最小割:将若干条有向边断开使得源点汇点不连通,这些有向边的边权和最小值。
首先注意到我们只可能会在 \(A\) 中断掉一条边 \(A_x\to A_{x+1}\),具体证明就是如果断了两条边,那么后面那条边断了也是白断,因为源点无法通过 \(A\) 中的点到达后面的边,\(B\) 又没有向 \(A\) 连的边,因此显然不优。同理,\(B\) 中也只会断掉一条边。
设 \(f_{x,y}\) 表示 \(A\) 中断掉 \(A_x\to A_{x+1}\),\(B\) 中断掉 \(B_y\to B_{y+1}\) 时最小割大小,此时我们需要将 \(A_{1,...,x}\) 到 \(B_{y+1,...,n}\) 中所有点都要断掉,因此有计算式:
其中 \(a_x\) 表示 \(A_x\to A_{x+1}\) 的边权,\(b_y\) 类似,\(d_{i,j}\) 表示 \(A_i\to B_j\) 的边权,没有记为 0。
然后注意到修改操作中只会修改 \(a_x\),因此后面这坨东西对于每一个 \(x\) 实际上是定的,令 \(c_x=b_y+\sum_{i\le x\cap j>y}d_{i,j}\),考虑如何快速维护 \(c_x\)。
考虑从小到大枚举 \(x\),在 \(\{b_n\}\) 上建立一棵线段树,每个节点维护当前枚举到 \(x\) 时区间 \(y\in[l,r]\) 内 \(c_x\) 的最小值,考虑枚举 \(x\) 出边 \(x\to y\),注意到会被影响到的实际上是个前缀 \([1,y-1]\),区间修改即可,改完后 \(c_x\) 就是根节点最小值。
于是问题变成了每次求 \(\min\{a_x+c_x\}\),随便维护一下就好了。
但是有个讨厌的地方,就是可能 A 里面可以没有点断掉,B 里面也可以没有点断掉,这样和上面分析过程不符(因为上面要求两边必须都要断),因此在两边另外连两条边 \(A_n\to A_{n+1},B_0\to B_1\),边权全是 0,表示如果没有边断掉那么就断这两条边,你会发现这样本质上没有改变图的任何性质但是有断边了。
代码里面是将所有 \(B\) 点往后移了一个位置然后令 \(B_0\) 重标号为 1。
Code:
/*
========= Plozia =========
Author:Plozia
Problem:CF903G Yet Another Maxflow Problem
Date:2022/10/25
========= Plozia =========
*/
#include <bits/stdc++.h>
typedef long long LL;
const int MAXN = 2e5 + 5;
int n, m, Head[MAXN], cntEdge, q;
LL a[MAXN], b[MAXN], c[MAXN];
struct node { int To; LL val; int Next; } Edge[MAXN << 1];
struct SgT { LL Minn, tag; } tree[MAXN << 2];
#define Minn(p) tree[p].Minn
#define tag(p) tree[p].tag
int Read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
return sum * fh;
}
void add(int x, int y, int z) { ++cntEdge; Edge[cntEdge] = (node){y, z, Head[x]}; Head[x] = cntEdge; }
LL Min(LL fir, LL sec) { return (fir < sec) ? fir : sec; }
void Update(int p) { Minn(p) = Min(Minn(p << 1), Minn(p << 1 | 1)); }
void Spread(int p)
{
Minn(p << 1) += tag(p), Minn(p << 1 | 1) += tag(p);
tag(p << 1) += tag(p), tag(p << 1 | 1) += tag(p); tag(p) = 0;
}
void Build(int p, int lp, int rp)
{
tag(p) = 0; if (lp == rp) { Minn(p) = b[lp]; return ; } int mid = (lp + rp) >> 1;
Build(p << 1, lp, mid); Build(p << 1 | 1, mid + 1, rp); Update(p);
}
void Add(int p, int l, int r, LL x, int lp, int rp)
{
if (lp >= l && rp <= r) { Minn(p) += x, tag(p) += x; return ; }
int mid = (lp + rp) >> 1; Spread(p);
if (l <= mid) Add(p << 1, l, r, x, lp, mid);
if (r > mid) Add(p << 1 | 1, l, r, x, mid + 1, rp);
Update(p);
}
int main()
{
n = Read(), m = Read(), q = Read();
for (int i = 1; i < n; ++i) a[i] = Read(), b[i + 1] = Read();
for (int i = 1; i <= m; ++i) { int x = Read(), y = Read(), z = Read(); add(x, y, z); }
Build(1, 1, n);
for (int i = 1; i <= n; ++i)
{
for (int j = Head[i]; j; j = Edge[j].Next) Add(1, 1, Edge[j].To, Edge[j].val, 1, n);
c[i] = Minn(1);
}
for (int i = 1; i <= n; ++i) b[i] = a[i] + c[i];
Build(1, 1, n); printf("%lld\n", Minn(1));
for (int i = 1; i <= q; ++i)
{
int x = Read(), y = Read(); Add(1, x, x, y - a[x], 1, n); a[x] = y;
printf("%lld\n", Minn(1));
}
return 0;
}