BZOJ2040[2009国家集训队]拯救Protoss的故乡——模拟费用流+线段树+树链剖分
题目描述
在星历2012年,星灵英雄Zeratul预测到他所在的Aiur行星在M天后会发生持续性暴雨灾害,尤其是他们的首都。而Zeratul作为星灵族的英雄,当然是要尽自己最大的努力帮助星灵族渡过这场自然灾害。要渡过这场自然灾害,Zeratul自然要安排很多很多事情,其中一件就是将雨水疏导到大海里去。星灵族在重建家园的时候建造了N条河流,这些河流连接了共N+1个城市,当然其中包括了星灵首都。城市的编号为0…N,星灵首都的编号为0。当然水流是有方向的,除了星灵首都以外,其余的城市都有且仅有一条河流流入。如果一个城市没有流出去的河流,那么这个城市就是一个沿海城市,可以认为流入这个城市的河流是直接流入大海的。聪明的星灵族在建造河流的时候是不会让其出现环状结构的,也就是说所有的河流都是能够流入大海的。每一条河流都是有一个最大流量的,一旦超过这个最大流量,就会发生洪水灾害。因此从星灵首都流入大海的总水流量是有一个最大值的。例如有3条河流:第一条是从城市0流向城市1,最大流量为4;第二条是从城市1流向城市2,最大流量为2;第三条是从城市1流向城市3,最大流量为3。此时从星灵首都(城市0)流入大海的总水流量最大为4,方案有两种: A. 第一条河流的流量为4,第二条河流的流量为2,第三条河流的流量为2。 B. 第一条河流的流量为4,第二条河流的流量为1,第三条河流的流量为3。由于离暴雨到来还有时间,因此Zeratul可以扩大某些河流的最大流量来使得从星灵首都流入大海的总水流量增大。比如将上面这个例子的第一条河流的最大流量增大1,那么从星灵首都流入大海的总流水量也可以增大1,变成了5。当然将河流的最大流量扩大是需要时间的,将一条河流的最大流量扩大1所需要的时间为1天。离暴雨到来还有M天,因此Zeratul也有M天的时间来扩大河流的最大流量。不过由于河流周围的地形限制,每条河流并不是能够无限扩大的,因此每条河流的可以扩大到的最大流量也是有限制的。而此时Zeratul正忙着安排别的工作,因此他将这个艰巨的任务交给了你。你需要安排一种方案,使得从星灵首都流入大海的总水流量最大。不过现在你只需要告诉Zeratul这个最大值即可。
输入
第一行包含一个正整数N和一个非负整数M。其中N表示河流的个数,M表示离暴雨到来还剩下的天数。接下来N行,每行四个正整数U、V、A、B。其中U和V为该河流所连接的两个城市,且河流的水流方向为从城市U流向城市V,A和B表示该河流当前的最大流量和可以扩大到的最大流量的上限。输入数据保证合法。
输出
仅包含一个整数,表示从星灵首都流入大海的最大总水流量。
样例输入
0 1 4 8
0 4 1 6
1 2 2 10
1 3 3 5
4 5 6 6
样例输出
提示
将第一条河流的最大流量扩大1,变为5。将第二条河流的最大流量扩大5,变为6。第三条河流的最大流量不扩大,仍然为2。第四条河流的最大流量不扩大,仍然为3。第五条河流的最大流量不扩大,仍然为6。此时从星灵首都流入大海的总水流量为6+3+2=11。 1≤A≤B≤100000 N< = 10000 M< = 1000000
第一段为部分分做法及满分做法的铺垫,可以直接看第二段QvQ。
对于部分分数据我们可以用树形$DP$做,即$f[i][j]$表示$i$节点子树中耗费$j$时间能得到的最大流量,枚举$i$的子节点$to$可以得到方程$f[i][j]=max(f[i][j],f[i][j-k-l]+min(a[to]+l,f[to][k],b[to]))$($a[to],b[to]$表示$i$到$to$的两种边权)。这样的时间复杂度是$O(nm^3)$。但可以发现$min(a[to]+l,f[to][k],b[to])$与$i$无关,我们可以用$g[to][j+k]$来记录$min(a[to]+l,f[to][k],b[to])$的最大值,这样得到转移方程$f[i][j]=max(f[i][j],f[i][j-k]+g[to][k]),g[to][k]=max(min(a[to]+l,b[to],f[to][k-l])$,时间复杂度降到了$O(nm^2)$。但如果我们不关注这是树的问题可以发现这实际上就是一个最小费用最大流,将每条边拆成两条边,一条边容量为$a$,费用为$0$;另一条边容量为$b-a$,费用为$1$。这样时间复杂度降到了$O(n^2)$。
这时我们再关注它是一棵树时就可以利用树的一些特性来对费用流进行优化:可以发现到每个叶子结点(即汇点)的路径只有一条,而根据费用流原理:每次选择一条费用最小的路径并将其跑到满流。我们可以模拟这个过程:记录根节点到每个叶子结点的费用的最小值(假设这个点为$x$),那么这一次增广的路径就是根到$x$的路径,流量就是路径上的容量最小值。我们将每条边的初始容量设为$a$,如果一次增广中容量最小值的边为$(u,v)$,容量为$f$,那么整条路径上所有边的容量都要减少$f$。如果当前这条边费用为$0$,我们将这条边容量变为$b-a$,这条边下端点子树中的所有叶子结点的费用就要$+1$;如果当前这条边费用为$1$,那么说明这条边下端点子树中所有叶子结点都不能再增广了,我们将子树中所有叶子结点的费用设为$INF$。在每次增广时可能存在有多条边同时满流的情况,我们只需要对一条边进行修改即可,其他边在下几次增广时就会相继被修改。
那么我们需要完成的操作有:
1、区间修改一段连续叶子结点的权值
2、查询所有叶子结点的权值最小值
3、查询一个叶子结点到根路径上的容量最小值
4、修改一条边的容量
5、将一个叶子节点到根路径上的容量减少
我们将叶子结点按$dfs$序排序,那么一个点子树中的叶子结点一定是连续的一段,前两个操作使用线段树即可完成;后三个操作使用LCT或树链剖分+线段树就可解决。
因为每一次增广都会让一条边满流,而每条边最多满流$2$次,所以时间复杂度为$O(nlog_{n}^2)$。
#include<set> #include<map> #include<queue> #include<stack> #include<cmath> #include<cstdio> #include<bitset> #include<vector> #include<cstring> #include<iostream> #include<algorithm> #define ll long long #define pr pair<int,int> using namespace std; int L[10010]; int R[10010]; pr mx[80010]; int add[80010]; int son[10010]; int top[10010]; int dep[10010]; int s[10010]; int a[10010]; int b[10010]; int q[10010]; int size[10010]; int f[10010]; int va[10010]; int vb[10010]; int head[10010]; int to[10010]; int next[10010]; pr mn[80010]; int red[80010]; int sig[80010]; int t[10010]; int cnt; int u,v; int tot; int x,y; int n,m; int dfn; int flow; inline void add_edge(int u,int v,int x,int y) { next[++tot]=head[u]; head[u]=tot; to[tot]=v; va[tot]=x; vb[tot]=y; } inline void dfs(int x) { size[x]=1; L[x]=cnt+1; for(int i=head[x];i;i=next[i]) { f[to[i]]=x; dep[to[i]]=dep[x]+1; a[to[i]]=va[i]; b[to[i]]=vb[i]; dfs(to[i]); size[x]+=size[to[i]]; if(size[to[i]]>size[son[x]]) { son[x]=to[i]; } } if(!head[x]) { cnt++; t[cnt]=x; } R[x]=cnt; } inline void dfs2(int x,int tp) { top[x]=tp; s[x]=++dfn; q[dfn]=x; if(son[x]) { dfs2(son[x],tp); } for(int i=head[x];i;i=next[i]) { if(to[i]!=son[x]) { dfs2(to[i],to[i]); } } } inline pr cmp(pr x,pr y) { return x.first<y.first?x:y; } inline void updata(int rt) { mx[rt]=cmp(mx[rt<<1],mx[rt<<1|1]); } inline void downdata(int rt) { if(add[rt]) { add[rt<<1]+=add[rt]; add[rt<<1|1]+=add[rt]; mx[rt<<1].first+=add[rt]; mx[rt<<1|1].first+=add[rt]; add[rt]=0; } } inline void build2(int rt,int l,int r) { if(l==r) { mx[rt]=make_pair(0,t[l]); return ; } int mid=(l+r)>>1; build2(rt<<1,l,mid); build2(rt<<1|1,mid+1,r); updata(rt); } inline void increase(int rt,int l,int r,int L,int R,int k) { if(L<=l&&r<=R) { add[rt]+=k; mx[rt].first+=k; return ; } downdata(rt); int mid=(l+r)>>1; if(L<=mid) { increase(rt<<1,l,mid,L,R,k); } if(R>mid) { increase(rt<<1|1,mid+1,r,L,R,k); } updata(rt); } inline void pushup(int rt) { mn[rt]=cmp(mn[rt<<1],mn[rt<<1|1]); } inline void pushdown(int rt) { if(red[rt]) { red[rt<<1]+=red[rt]; red[rt<<1|1]+=red[rt]; mn[rt<<1].first-=red[rt]; mn[rt<<1|1].first-=red[rt]; red[rt]=0; } } inline void build(int rt,int l,int r) { if(l==r) { mn[rt]=make_pair(a[q[l]],q[l]); return ; } int mid=(l+r)>>1; build(rt<<1,l,mid); build(rt<<1|1,mid+1,r); pushup(rt); } inline pr query_min(int rt,int l,int r,int L,int R) { if(L<=l&&r<=R) { return mn[rt]; } pushdown(rt); int mid=(l+r)>>1; if(L>mid) { return query_min(rt<<1|1,mid+1,r,L,R); } else if(R<=mid) { return query_min(rt<<1,l,mid,L,R); } else { return cmp(query_min(rt<<1,l,mid,L,R),query_min(rt<<1|1,mid+1,r,L,R)); } } inline void reduce(int rt,int l,int r,int L,int R,int k) { if(L<=l&&r<=R) { red[rt]+=k; mn[rt].first-=k; return ; } pushdown(rt); int mid=(l+r)>>1; if(L<=mid) { reduce(rt<<1,l,mid,L,R,k); } if(R>mid) { reduce(rt<<1|1,mid+1,r,L,R,k); } pushup(rt); } inline void change(int rt,int l,int r,int k) { if(l==r) { if(!sig[rt]) { sig[rt]=1; mn[rt]=make_pair(b[q[l]]-a[q[l]],q[l]); increase(1,1,cnt,L[q[l]],R[q[l]],1); return ; } else { mn[rt]=make_pair(0,q[l]); increase(1,1,cnt,L[q[l]],R[q[l]],10010); return ; } } pushdown(rt); int mid=(l+r)>>1; if(k<=mid) { change(rt<<1,l,mid,k); } else { change(rt<<1|1,mid+1,r,k); } pushup(rt); } inline pr query(int x) { pr res=make_pair(1000000000,0); while(top[x]!=n+1) { res=cmp(res,query_min(1,1,n+1,s[top[x]],s[x])); x=f[top[x]]; } if(s[top[x]]+1>s[x]) { return res; } res=cmp(res,query_min(1,1,n+1,s[top[x]]+1,s[x])); return res; } inline void reduce_flow(int x,int k) { while(top[x]!=n+1) { reduce(1,1,n+1,s[top[x]],s[x],k); x=f[top[x]]; } if(s[top[x]]+1>s[x]) { return ; } reduce(1,1,n+1,s[top[x]]+1,s[x],k); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d%d%d%d",&u,&v,&x,&y); if(!u) { u=n+1; } add_edge(u,v,x,y); } dfs(n+1); dfs2(n+1,n+1); build(1,1,n+1); build2(1,1,cnt); while(1) { pr ans=mx[1]; pr res=query(ans.second); if(ans.first>n) { break; } if(m>=res.first*ans.first) { m-=res.first*ans.first; flow+=res.first; reduce_flow(ans.second,res.first); change(1,1,n+1,s[res.second]); } else { int sum=m/ans.first; m-=sum*ans.first; flow+=sum; break; } } printf("%d",flow); }