题目大意
给定一个2n个节点的图,其中n个点在A集,n个点在B集。且称A集第i个点为ai(B集类似)。每个ai(i < n)向ai+1连一条给定容量的边(B也一样),还有m条边从ax连到ay,容量给定。
有q次操作,每次修改一条ax连向ax+1的边的容量(x和容量给定)。你需要对每次操作以及操作前输出以a1为源点,bn为汇点的最大流。
n,m,q≤200000
分析
首先最大流=最小割
考虑没有修改怎么做。
可以枚举割掉连接ai和ai+1的边(i=n表示不割),然后只需要考虑m条横跨边中起点编号小于等于i的边。这时枚举割掉连接bj-1与bj的边(j=1表示不割),答案只需要再加上横跨边中终点编号大于等于j的边。
我们发现顺序枚举i时,割B集边的最小值可以用线段树来维护
接下来设Ans(i)表示割掉ai与ai+1连边的答案。由于只修改A集的边,我们发现每次修改只有Ans(x)变化,这里也可以线段树维护
时间复杂度 O(nlogn)
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=2e5+5,M=524444;
typedef long long LL;
int n,m,q,h[N],e[N],nxt[N],v[N],A[N],B[N];
LL t[M],tag[M],Now[N];
char c;
int read()
{
int x=0,sig=1;
for (c=getchar();c<'0' || c>'9';c=getchar()) if (c=='-') sig=-1;
for (;c>='0' && c<='9';c=getchar()) x=x*10+c-48;
return x*sig;
}
void Add(int l,int r,int a,int b,LL v,int x)
{
if (l==a && r==b)
{
tag[x]+=v; t[x]+=v; return;
}
int mid=l+r>>1;
if (tag[x]!=0)
{
tag[x<<1]+=tag[x]; t[x<<1]+=tag[x]; tag[x<<1|1]+=tag[x]; t[x<<1|1]+=tag[x];
tag[x]=0;
}
if (b<=mid) Add(l,mid,a,b,v,x<<1);else if (a>mid) Add(mid+1,r,a,b,v,x<<1|1);else
{
Add(l,mid,a,mid,v,x<<1); Add(mid+1,r,mid+1,b,v,x<<1|1);
}
t[x]=min(t[x<<1],t[x<<1|1]);
}
int main()
{
n=read(); m=read(); q=read();
for (int i=1;i<n;i++)
{
A[i]=read(); B[i+1]=read(); Add(1,n,i+1,i+1,B[i+1],1);
}
for (int i=1,x;i<=m;i++)
{
x=read(); e[i]=read(); v[i]=read();
nxt[i]=h[x]; h[x]=i;
}
for (int i=1;i<=n;i++)
{
for (int j=h[i];j;j=nxt[j]) Add(1,n,1,e[j],v[j],1);
Now[i]=A[i]+t[1];
}
memset(t,0,sizeof(t));
memset(tag,0,sizeof(tag));
for (int i=1;i<=n;i++) Add(1,n,i,i,Now[i],1);
printf("%lld\n",t[1]);
for (int x,y;q--;)
{
x=read(); y=read();
Add(1,n,x,x,y-A[x],1); A[x]=y;
printf("%lld\n",t[1]);
}
return 0;
}