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);
}
BZOJ2040[2009国家集训队]拯救Protoss的故乡——模拟费用流+线段树+树链剖分的更多相关文章
- BZOJ2040 : [2009国家集训队]拯救Protoss的故乡
以根为原点,所有叶子为汇点建立网络. 对于一条边$(x,y,A,B)$,$x$向$y$连边,容量$A$,费用0,再连边,容量$B-A$,费用1. 然后不断增广,直到费用达到$M$为止的最大流即为答案. ...
- BZOJ 2039: [2009国家集训队]employ人员雇佣
2039: [2009国家集训队]employ人员雇佣 Time Limit: 20 Sec Memory Limit: 259 MBSubmit: 1369 Solved: 667[Submit ...
- BZOJ 2038: [2009国家集训队]小Z的袜子(hose) [莫队算法]【学习笔记】
2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec Memory Limit: 259 MBSubmit: 7687 Solved: 3516[Subm ...
- BZOJ 2038: [2009国家集训队]小Z的袜子(hose)
2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec Memory Limit: 259 MBSubmit: 7676 Solved: 3509[Subm ...
- 莫队算法 2038: [2009国家集训队]小Z的袜子(hose)
链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2038 2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 ...
- BZOJ 2038 [2009国家集训队]小Z的袜子 莫队
2038: [2009国家集训队]小Z的袜子(hose) 题目连接: http://www.lydsy.com/JudgeOnline/problem.php?id=2038 Descriptionw ...
- Bzoj 2038: [2009国家集训队]小Z的袜子(hose) 莫队,分块,暴力
2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec Memory Limit: 259 MBSubmit: 5763 Solved: 2660[Subm ...
- BZOJ2038: [2009国家集训队]小Z的袜子(hose) -- 莫队算法 ,,分块
2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec Memory Limit: 259 MBSubmit: 3577 Solved: 1652[Subm ...
- BZOJ 2038: [2009国家集训队]小Z的袜子(hose) ( 莫队 )
莫队..先按sqrt(n)分块, 然后按块的顺序对询问排序, 同块就按右端点排序. 然后就按排序后的顺序暴力求解即可. 时间复杂度O(n1.5) --------------------------- ...
随机推荐
- 【原创】惊!史上最全的select加锁分析(Mysql)
引言 大家在面试中有没遇到面试官问你下面六句Sql的区别呢 select * from table where id = ? select * from table where id < ? s ...
- Linux Namespace : Network
Network namespace 在逻辑上是网络堆栈的一个副本,它有自己的路由.防火墙规则和网络设备.默认情况下,子进程继承其父进程的 network namespace.也就是说,如果不显式创建新 ...
- python实现本地图片上传到服务区
本地图片上传到服务器,其本质上来讲,就是读取本地图片,复制到服务器,并返回服务器url 前端代码,用的form表单提交,form表单中包含两个文件选择表单元素,选择文件,点击提交按钮,提交form表单 ...
- Linux命令(一)
需要用Xshell连接Linux时: 先在终端输入命令:service sshd start(开启ssh服务) 1.netstat -tnl:查看端口状态的命令(如 查看22端口) 2.servi ...
- H5 65-清除浮动方式一
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Linux进程与线程的区别
进程与线程的区别,早已经成为了经典问题.自线程概念诞生起,关于这个问题的讨论就没有停止过.无论是初级程序员,还是资深专家,都应该考虑过这个问题,只是层次角度不同罢了.一般程序员而言,搞清楚二者的概念, ...
- hibernate多对多 一对多 及简单入门 主键生成策略
Hibernate简单使用 入门 通过hibernate的 一对多 多对多轻松看懂hibernate配置 (不使用注解) hibernate对jdbc访问数据库的代码进行轻量级封装,简化重复代码 减少 ...
- echarts使用笔记三:柱子对比
app.title = '坐标轴刻度与标签对齐'; option = { title : { //标题 x : 'center', y : 5, text : '对比图' //换行用 \n }, le ...
- Latex常用
插入罗马数字 \newcommand{\RNum}[1]{\uppercase\expandafter{\romannumeral #1\relax}} 然后在正文里面就可以用\RNum{}来添加罗马 ...
- js判断数组是否包含某个字符串变量的实例
最近碰到一个这样的现象,后台返回的数据中,数组里面有一些有变量值,有一些没有变量值. 举个例子,比如后台返回的例子是这样的: var arr=[ { "status":" ...