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}\) 中所有点都要断掉,因此有计算式:

\[f_{x,y}=a_x+b_y+\sum_{i\le x\cap j>y}d_{i,j} \]

其中 \(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;
}
posted @ 2022-10-25 12:45  Plozia  阅读(32)  评论(0编辑  收藏  举报