[Codeforces 903G]Yet Another Maxflow Problem(线段树)

Address

Meaning

  • 一张图, 2 n 2n 2n 个点,分成两部 A A A B B B ,每部 n n n 个点
  • 其中 A i A_i Ai A i + 1 A_{i+1} Ai+1 有容量为 x i x_i xi 的边( 1 ≤ i &lt; n 1\le i&lt;n 1i<n
  • B i B_i Bi B i + 1 B_{i+1} Bi+1 有容量为 y i y_i yi 的边( 1 ≤ i &lt; n 1\le i&lt;n 1i<n
  • m m m 个三元组 ( x , y , z ) (x,y,z) (x,y,z) 表示 A x A_x Ax B y B_y By 有容量为 z z z 的边
  • A 1 A_1 A1 B n B_n Bn 的最大流
  • 之后有 q q q 个操作,每个操作给出两个参数 v v v w w w ,把 x v x_v xv A v A_v Av A v + 1 A_{v+1} Av+1 的边的容量)修改成 w w w
  • 每次修改之后需要重新求出原图的最大流
  • 2 ≤ n , m ≤ 2 × 1 0 5 2\le n,m\le 2\times 10^5 2n,m2×105
  • 0 ≤ q ≤ 2 × 1 0 5 0\le q\le 2\times10^5 0q2×105
  • 1 ≤ x i , y i , z , w ≤ 1 0 9 1\le x_i,y_i,z,w\le10^9 1xi,yi,z,w109

Solution

  • 注:下面一律 x n = y 0 = 0 x_n=y_0=0 xn=y0=0 c a p ( u , v ) cap(u,v) cap(u,v) u u u v v v 的边的容量
  • 一道比较不错的数据结构题
  • 首先最大流等于最小割
  • 然后我们考虑这个特殊的图,最小割是否有特殊性
  • 很容易得出,最小割一定是
  • (1)在 A A A 部中割掉一条 A i A_i Ai A i + 1 A_{i+1} Ai+1 的边(在 A A A 部也可以什么边都不割,这时视为割边 ( A n , A n + 1 ) (A_n,A_{n+1}) (An,An+1)
  • (2)在 B B B 部中割掉一条 B j − 1 B_{j-1} Bj1 B j B_j Bj 的边(同样地可以在 B B B 部中不割任何边,但这时视为割边 ( B 0 , B 1 ) (B_0,B_1) (B0,B1)
  • (3)在上面两个的基础上,把所有满足 u ≤ i , v ≥ j u\le i,v\ge j ui,vj 的边 ( A u , B v ) (A_u,B_v) (Au,Bv) 全部割掉
  • 我们看到(3)之后可以得出一个思路,设
  • f [ i ] = min ⁡ j = 1 n ( y j − 1 + ∑ u = 1 i ∑ v = j n c a p ( A u , B v ) ) f[i]=\min_{j=1}^n(y_{j-1}+\sum_{u=1}^i\sum_{v=j}^ncap(A_u,B_v)) f[i]=j=1minn(yj1+u=1iv=jncap(Au,Bv))
  • f [ i ] f[i] f[i] 的意义即是在钦定割边 ( A i , A i + 1 ) (A_i,A_{i+1}) (Ai,Ai+1) 的情况下的最小割(不包括边 ( A i , A i + 1 ) (A_i,A_{i+1}) (Ai,Ai+1)
  • 如果我们能够求出 f [ 1 … n ] f[1\dots n] f[1n] ,那么我们的问题就能解决了
  • 最大流(最小割)就是
  • min ⁡ i = 1 n ( f [ i ] + x i ) \min_{i=1}^n(f[i]+x_i) i=1minn(f[i]+xi)
  • 线段树维护 f [ i ] + x i f[i]+x_i f[i]+xi 即可
  • 然后我们考虑如何求 f [ 1 … n ] f[1\dots n] f[1n]
  • 考虑从 1 1 1 n n n 按顺序枚举 i i i
  • 开一棵线段树
  • j j j 位置的叶子节点储存的是 y j − 1 + ∑ u = 1 i ∑ v = j n c a p ( A u , B v ) y_{j-1}+\sum_{u=1}^i\sum_{v=j}^ncap(A_u,B_v) yj1+u=1iv=jncap(Au,Bv) ,意义是钦定割边 ( A i , A i + 1 ) (A_i,A_{i+1}) (Ai,Ai+1) ( B j − 1 , B j ) (B_{j-1},B_j) (Bj1,Bj) 之后还需要割掉多大的容量,需要割掉的边容量之和再加上 c a p ( B j − 1 , B j ) cap(B_{j-1},B_j) cap(Bj1,Bj) y j y_j yj
  • 线段树维护区间最小值
  • 每次 i i i i − 1 i-1 i1 加一过来时,枚举边 ( A i , B j ) (A_i,B_j) (Ai,Bj) ,并在线段树上进行一次区间修改,区间 [ 1 , j ] [1,j] [1,j] 加上 c a p ( A i , B j ) cap(A_i,B_j) cap(Ai,Bj) ,这时 f [ i ] f[i] f[i] 为线段树全局最小值
  • 注意一开始叶子 j j j 存的值是 y j − 1 y_{j-1} yj1
  • 复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define p2 p << 1
#define p3 p << 1 | 1

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

typedef long long ll;

const int N = 2e5 + 5, M = N << 2;

int n, m, q, a[N], ecnt, nxt[N], adj[N], go[N], val[N];
ll T[M], add[M], b[N], ioi[N];

void add_edge(int u, int v, int w)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; val[ecnt] = w;
}

void build(int l, int r, int p)
{
	add[p] = 0;
	if (l == r) return (void) (T[p] = b[l]);
	int mid = l + r >> 1;
	build(l, mid, p2); build(mid + 1, r, p3);
	T[p] = Min(T[p2], T[p3]);
}

void down(int p)
{
	add[p2] += add[p]; add[p3] += add[p];
	add[p] = 0;
}

void upt(int p)
{
	T[p] = Min(T[p2] + add[p2], T[p3] + add[p3]);
}

void change(int l, int r, int s, int e, int v, int p)
{
	if (l == s && r == e) return (void) (add[p] += v);
	int mid = l + r >> 1; down(p);
	if (e <= mid) change(l, mid, s, e, v, p2);
	else if (s >= mid + 1) change(mid + 1, r, s, e, v, p3);
	else change(l, mid, s, mid, v, p2),
		change(mid + 1, r, mid + 1, e, v, p3);
	upt(p);
}

int main()
{
	int x, y, z;
	n = read(); m = read(); q = read();
	for (int i = 1; i < n; i++)
		a[i] = read(), b[i + 1] = read();
	while (m--) x = read(), y = read(), z = read(),
		add_edge(x, y, z);
	build(1, n, 1);
	for (int i = 1; i <= n; i++)
	{
		for (int e = adj[i], v = go[e]; e; e = nxt[e], v = go[e])
			change(1, n, 1, v, val[e], 1);
		ioi[i] = T[1] + add[1];
	}
	for (int i = 1; i <= n; i++) b[i] = a[i] + ioi[i];
	build(1, n, 1);
	std::cout << T[1] + add[1] << std::endl;
	while (q--)
	{
		x = read(); y = read();
		change(1, n, x, x, y - a[x], 1);
		a[x] = y;
		printf("%I64d\n", T[1] + add[1]);
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值