题目链接: https://codeforces.com/contest/903/problem/G
题意:
在理解网络流的前提下简化的题目大概是,源点向 连流量 , 向汇点连流量 ,对于每个 都有一条 以及 的边,流量均已知,同时告诉你 条 的流量已知的边。 现在要进行 次操作,每次操作改变一条 的边的流量,每次改变后问你当前的最大流。
做法:
表示 的题目不愧是 的题目,别说自己想了…连看人家的题解都看了半天才明白… (在自闭的边缘徘徊)
自己表述可能不太好,就借着大佬的博客内容稍微说一说。
最小割为什么被称之为最小割,因为:
在 侧的点中,若割掉了 ,那么所有的边 失去意义。
在 侧的点中,若割掉了 ,那么所有的边 失去意义
我们知道的是,在 侧和 侧中一定有一遍是满足上述条件的,只要一侧满足,那么另一侧一定能找到一个刚好符合的点。
我们假设 , 是符合我们要的两条边,那么 都是需要被割掉的边集。
我们从 侧的点 开始往 枚举,不断令该点之前的流量变为 来尝试,如果是从这个往后因为满流而断掉所需要的最大流量。
线段树里存放是的点 的合法流量,点 一点代表的是 ,表示我们在确定了 侧的 和 侧的 之后需要割的流量。 我们每次都取 侧的最小值即可。
我一开始的时候对这个做法很是疑惑,但是模了之后也慢慢理解了。。觉得很是神奇的一道题目。
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i = (int)a;i<=(int)b;i++)
#define lson rt<<1
#define rson rt<<1|1
#define mid (l+r)/2
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
const int maxn=200005;
ll mx[maxn<<2],laz[maxn<<2];
ll n,m,q,A[maxn],B[maxn],flow[maxn];
vector<pll> ve[maxn];
void build(int l,int r,int rt){
if(l==r) {
mx[rt]=(ll)B[l-1];
return ;
}
build(l,mid,lson);
build(mid+1,r,rson);
mx[rt]=min(mx[lson],mx[rson]);
}
void deal(int rt,ll v){
mx[rt]+=v,laz[rt]+=v;
}
void push_down(int rt){
if(laz[rt]){
deal(lson,laz[rt]);
deal(rson,laz[rt]);
laz[rt]=0ll;
}
}
void update(int l,int r,int rt,int ql,int qr,ll v){
if(ql<=l&&r<=qr){
deal(rt,v);
return ;
}
push_down(rt);
if(ql<=mid) update(l,mid,lson,ql,qr,v);
if(qr>mid) update(mid+1,r,rson,ql,qr,v);
mx[rt]=min(mx[lson],mx[rson]);
}
multiset<ll> S;
int main(){
scanf("%lld%lld%lld",&n,&m,&q);
rep(i,1,n-1) scanf("%lld%lld",&A[i],&B[i]);
rep(i,1,m){
ll x,y,c; scanf("%lld%lld%lld",&x,&y,&c);
ve[x].push_back({y,c});
}
build(1,n,1);
//printf("%d\n",ve[1].size());
rep(i,1,n){
for(auto p:ve[i]){
update(1,n,1,1,p.first,p.second);
}
flow[i]=mx[1];
S.insert(A[i]+flow[i]);
}
printf("%lld\n",*(S.begin()));
rep(k,1,q){
int x,y;scanf("%d%d",&x,&y);
S.erase(S.find(A[x]+flow[x]));
A[x]=y; S.insert(A[x]+flow[x]);
printf("%lld\n",*(S.begin()));
}
return 0;
}