题目描述

在星历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表示该河流当前的最大流量和可以扩大到的最大流量的上限。输入数据保证合法。

输出

仅包含一个整数,表示从星灵首都流入大海的最大总水流量。

样例输入

5 7
0 1 4 8
0 4 1 6
1 2 2 10
1 3 3 5
4 5 6 6

样例输出

11

提示

将第一条河流的最大流量扩大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的故乡——模拟费用流+线段树+树链剖分的更多相关文章

  1. BZOJ2040 : [2009国家集训队]拯救Protoss的故乡

    以根为原点,所有叶子为汇点建立网络. 对于一条边$(x,y,A,B)$,$x$向$y$连边,容量$A$,费用0,再连边,容量$B-A$,费用1. 然后不断增广,直到费用达到$M$为止的最大流即为答案. ...

  2. BZOJ 2039: [2009国家集训队]employ人员雇佣

    2039: [2009国家集训队]employ人员雇佣 Time Limit: 20 Sec  Memory Limit: 259 MBSubmit: 1369  Solved: 667[Submit ...

  3. BZOJ 2038: [2009国家集训队]小Z的袜子(hose) [莫队算法]【学习笔记】

    2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec  Memory Limit: 259 MBSubmit: 7687  Solved: 3516[Subm ...

  4. BZOJ 2038: [2009国家集训队]小Z的袜子(hose)

    2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec  Memory Limit: 259 MBSubmit: 7676  Solved: 3509[Subm ...

  5. 莫队算法 2038: [2009国家集训队]小Z的袜子(hose)

    链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2038 2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 ...

  6. BZOJ 2038 [2009国家集训队]小Z的袜子 莫队

    2038: [2009国家集训队]小Z的袜子(hose) 题目连接: http://www.lydsy.com/JudgeOnline/problem.php?id=2038 Descriptionw ...

  7. Bzoj 2038: [2009国家集训队]小Z的袜子(hose) 莫队,分块,暴力

    2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec  Memory Limit: 259 MBSubmit: 5763  Solved: 2660[Subm ...

  8. BZOJ2038: [2009国家集训队]小Z的袜子(hose) -- 莫队算法 ,,分块

    2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec  Memory Limit: 259 MBSubmit: 3577  Solved: 1652[Subm ...

  9. BZOJ 2038: [2009国家集训队]小Z的袜子(hose) ( 莫队 )

    莫队..先按sqrt(n)分块, 然后按块的顺序对询问排序, 同块就按右端点排序. 然后就按排序后的顺序暴力求解即可. 时间复杂度O(n1.5) --------------------------- ...

随机推荐

  1. IT程序员的抉择:我要离开帝都了

    不知不觉在北京已经漂泊了近5年了,共为3家公司打过工,其中有几十人的小公司,也有几万人的大公司.随着工作技能的提升和工作经验的积累,薪水自然也涨了不少,但是看着北京的房价.物价飞涨,感觉自己赚多少都是 ...

  2. 微软是如何让我再次爱上.Net Core和C#的

    “为什么你还想用ASP.NET,难道你还活在90年代吗?”这正是我的一位老同事在几年前我们即将开始的项目中我提出考虑使用ASP.NET时所说的话.当时我很大程度上认同他的看法,微软已经开发了伟大的开发 ...

  3. Python-常见面试题-持续更新

    1.请你简要介绍一下Python的生成器是什么 答:Python生成器是一个返回可以迭代对象的函数,可以被用作控制循环的迭代行为. 生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用 ...

  4. Python_内置函数2_44

    字符串类型代码执行: exec('print(123)') eval('print(123)') print(eval('1*2+3+4')) # 有返回值 print(exec('1+2+3+4') ...

  5. Python-类的特性(property)

    什么是特性property property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值 例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个 ...

  6. YCSB报": No such file or directory"异常

    异常信息如下: 文件路径.权限都没有问题. 上网遍寻无果,安装流程与官网一致,开始怀疑是环境问题,后来用别人能用的YCSB复制到本地,却能正常运行. 后来修改了ycsb文件,加了个空格,保存退出,再运 ...

  7. UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-25: ordinal not in range(128)

    python报错:UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-25: ordinal not in ...

  8. 01-学习vue前的准备工作

    起步 1.扎实的HTML/CSS/Javascript基本功,这是前置条件. 2.不要用任何的构建项目工具,只用最简单的<script>,把教程里的例子模仿一遍,理解用法.不推荐上来就直接 ...

  9. CentOS搭建OpenVPN以及WIN&Android&iOS的安装连接

    OpenVPNhttp://info.swufe.edu.cn/vpn/openvpn/#2 苹果.安卓智能手机openvpn的设置_百度经验https://jingyan.baidu.com/art ...

  10. css行内省略号、垂直居中

    应用场景分析: 一.当你的文字限定行数,超出部分的文字用省略号显示. (有两个使用场景:1.单行 2.多行) // 单行 overflow: hidden; text-overflow:ellipsi ...