题目

题目大意

给你一个树形的网络,每条边从父亲流向儿子。根节点为原点,叶子节点流向汇点,容量为无穷大。

可以给一些边扩大容量,最多总共扩大\(m\)容量。每条边的容量有上限。

求扩大容量后最大的最大流。


思考历程

隐隐约约地猜到正解跟树链剖分有什么关系,可是没有打,也没有时间打。

只能暴力DP来水分。

设\(h_{i,j}\)为\(i\)的父亲到\(i\)的最大流,扩大了\(j\)次容量。\(g_{i,j}\)为\(i\)到子树的最大流,扩大了\(j\)次容量。前者由后者和边的容量取最小值后得到。

转移方程显然。

这样水了\(70\)分,超过预期\(20\)分。


正解

有个费用流的做法:对于每个父亲到儿子连费用不同的两条边。扩大一次相当于增加一点费用。

跑最小费用最大流,每次选费用最小的路来增广,费用不超过\(m\)时的最大流即为答案。

这种做法是\(O(n^2)\)的。因为每次增广过后至少会有一条边满流,相当于删掉了这条边。

题解说期望得分\(100\)……我真的服……然而真的有人这么打就过了……

正解是利用树的性质来优化费用流……题解说用\(LCT\),我看打题解的人实在是太物流了。明明这题码量这么长,还打\(LCT\)?

接下来我们模拟费用流的过程:

  1. 找到一个从根节点到叶子节点的费用最小的路径。
  2. 求出这条路径的最大流量\(f\)(也就是路径上容量最小的边)。
  3. 统计入答案。
  4. 路径上的所有边的容量都减去\(f\)。
  5. 将修改过的路径上满流边找出来(即为容量为\(0\)的边)。分类讨论:如果费用为\(0\),那就修改它的费用为\(1\),并且修改它的容量;如果费用为\(1\),标记这条边不能再经过(也就是所有的后代都不能到达)。

当然,记得要特殊判断一下即将大于\(m\)的情况。

这个东西用树链剖分和线段树来维护。线段树上维护这些信息:最小的容量\(f\)及其儿子的编号\(numf\)(某条从上到下的路径上容量最小的边),还有最小的费用\(w\)及叶子节点的编号\(numw\)(这个费用为根节点到叶子节点路径上费用之和)。

操作\(1\)的时候,就是询问最小的\(w\)。

操作\(2\)的时候,就是当前节点到根路径上最小的\(f\)。

操作\(4\)的时候,就是当前节点到根路径上的所有边区间减\(f\)。

操作\(5\)的时候,就是将当前节点能到的所有叶子节点的费用区间加\(1\)或无限大。

就是这些操作了……


代码

好长……还好树链剖分和线段树好打……

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#include <cassert>
#define N 10010
#define INF 1000000000
int n,m;
struct EDGE{
int to;
EDGE *las;
} e[N];
int ne;
EDGE *last[N];
bool leaf[N];
int a[N],b[N];
int fa[N],siz[N],hs[N];
int top[N],dfn[N],nowdfn,to_num[N];
int noww[N];
void dfs1(int x){
siz[x]=1;
for (EDGE *ei=last[x];ei;ei=ei->las){
fa[ei->to]=x;
dfs1(ei->to);
siz[x]+=siz[ei->to];
if (siz[ei->to]>siz[hs[x]])
hs[x]=ei->to;
}
if (siz[x]==1)
leaf[x]=1;
}
void dfs2(int x,int t){
top[x]=t;
dfn[x]=++nowdfn;
to_num[nowdfn]=x;
if (hs[x])
dfs2(hs[x],t);
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=hs[x])
dfs2(ei->to,ei->to);
}
struct Node{
int f,numf;
int w,numw;
int tagf,tagw;
} seg[N*4];
inline void updatef(int k){
if (seg[k<<1].f<=seg[k<<1|1].f)
seg[k].f=seg[k<<1].f,seg[k].numf=seg[k<<1].numf;
else
seg[k].f=seg[k<<1|1].f,seg[k].numf=seg[k<<1|1].numf;
}
inline void updatew(int k){
if (seg[k<<1].w<=seg[k<<1|1].w)
seg[k].w=seg[k<<1].w,seg[k].numw=seg[k<<1].numw;
else
seg[k].w=seg[k<<1|1].w,seg[k].numw=seg[k<<1|1].numw;
}
inline void pushdown(int k){
if (seg[k].tagf){
seg[k<<1].f+=seg[k].tagf;
seg[k<<1|1].f+=seg[k].tagf;
seg[k<<1].tagf+=seg[k].tagf;
seg[k<<1|1].tagf+=seg[k].tagf;
seg[k].tagf=0;
}
if (seg[k].tagw){
if (seg[k].tagw>=INF)
seg[k<<1].w=seg[k<<1|1].w=seg[k<<1].tagw=seg[k<<1|1].tagw=INF;
else{
seg[k<<1].w+=seg[k].tagw;
seg[k<<1|1].w+=seg[k].tagw;
seg[k<<1].tagw+=seg[k].tagw;
seg[k<<1|1].tagw+=seg[k].tagw;
seg[k].tagw=0;
}
}
}
void build(int k,int l,int r){
if (l==r){
if (leaf[to_num[l]])
seg[k]={a[to_num[l]],to_num[l],0,to_num[l],0,0};
else
seg[k]={a[to_num[l]],to_num[l],INF,0,0,0};
return;
}
int mid=l+r>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
updatef(k),updatew(k);
}
void changef(int k,int l,int r,int st,int en,int c){
if (st<=l && r<=en){
seg[k].f+=c;
seg[k].tagf+=c;
return;
}
pushdown(k);
int mid=l+r>>1;
if (st<=mid)
changef(k<<1,l,mid,st,en,c);
if (mid<en)
changef(k<<1|1,mid+1,r,st,en,c);
updatef(k);
}
void changew(int k,int l,int r,int st,int en,int c){
if (st<=l && r<=en){
if (c>=INF)
seg[k].w=seg[k].tagw=INF;
else{
seg[k].w+=c;
seg[k].tagw+=c;
}
return;
}
pushdown(k);
int mid=l+r>>1;
if (st<=mid)
changew(k<<1,l,mid,st,en,c);
if (mid<en)
changew(k<<1|1,mid+1,r,st,en,c);
updatew(k);
}
pair<int,int> un(const pair<int,int> &a,const pair<int,int> &b){return a.first<=b.first?a:b;}
pair<int,int> queryf(int k,int l,int r,int st,int en){
if (st<=l && r<=en)
return {seg[k].f,seg[k].numf};
pushdown(k);
int mid=l+r>>1;
pair<int,int> res(INF,0);
if (st<=mid)
res=un(res,queryf(k<<1,l,mid,st,en));
if (mid<en)
res=un(res,queryf(k<<1|1,mid+1,r,st,en));
return res;
}
int queryw(int k,int l,int r,int x){
if (l==r)
return seg[k].tagw;
pushdown(k);
int mid=l+r>>1;
if (x<=mid)
return queryw(k<<1,l,mid,x);
return queryw(k<<1|1,mid+1,r,x);
}
inline int flow(int x){
int res=INT_MAX;
for (;x;x=fa[top[x]])
res=min(res,queryf(1,1,n,dfn[top[x]],dfn[x]).first);
return res;
}
inline void find(int x,int c){
for (int y=x;y;y=fa[top[y]])
changef(1,1,n,dfn[top[y]],dfn[y],-c);
for (;x;x=fa[top[x]]){
while (x){
pair<int,int> tmp=queryf(1,1,n,dfn[top[x]],dfn[x]);
if (tmp.first)
break;
if (noww[tmp.second]==0){
noww[tmp.second]=1;
changew(1,1,n,dfn[tmp.second],dfn[tmp.second]+siz[tmp.second]-1,1);
changef(1,1,n,dfn[tmp.second],dfn[tmp.second],b[tmp.second]);
}
else{
changew(1,1,n,dfn[tmp.second],dfn[tmp.second]+siz[tmp.second]-1,INF);
x=fa[tmp.second];
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
a[1]=INT_MAX;
for (int i=1;i<=n;++i){
int u,v;
scanf("%d%d",&u,&v);
u++,v++;
e[ne]={v,last[u]};
last[u]=e+ne++;
scanf("%d%d",&a[v],&b[v]);
b[v]=min(b[v]-a[v],m);
}
n++;
dfs1(1),dfs2(1,1);
build(1,1,n);
int ans=0;
while (1){
int x=seg[1].numw,cost=seg[1].w;
if (cost>=INF)
break;
int plus=flow(x);
if (m<cost*plus){
ans+=m/cost;
break;
}
m-=cost*plus;
ans+=plus;
find(x,plus);
}
printf("%d\n",ans);
return 0;
}

总结

有的时候网络流是可以用数据结构维护的……

[JZOJ1904] 【2010集训队出题】拯救Protoss的故乡的更多相关文章

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

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

  2. BZOJ2040[2009国家集训队]拯救Protoss的故乡——模拟费用流+线段树+树链剖分

    题目描述 在星历2012年,星灵英雄Zeratul预测到他所在的Aiur行星在M天后会发生持续性暴雨灾害,尤其是他们的首都.而Zeratul作为星灵族的英雄,当然是要尽自己最大的努力帮助星灵族渡过这场 ...

  3. [JZOJ1901] 【2010集训队出题】光棱坦克

    题目 题目大意 给你个平面上的一堆点,问序列\({p_i}\)的个数. 满足\(y_{p_{i-1}}>y_{p_i}\)并且\(x_{p_i}\)在\(x_{p_i-1}\)和\(x_{p_i ...

  4. [JZOJ1900] 【2010集训队出题】矩阵

    题目 题目大意 题目化简一下,就变成: 构造一个\(01\)数列\(A\),使得\(D=\sum A_iA_jB_{i,j}-\sum A_iC_i\)最大. 问这个最大的\(D\)是多少. 正解 其 ...

  5. [期望DP][纪中]【2010集训队出题】彩色圆环

    彩色圆环 感谢名单 十分感谢 JA_Ma 为我讲解了 \(T1\) 的 期望DP 的思想和推论. 十分感谢 SSL_LYF 为我解答了 \(T1\) 的 期望DP 的概率的大小问题. 十分感谢 SSL ...

  6. bzoj AC倒序

    Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...

  7. Noip前的大抱佛脚----赛前任务

    赛前任务 tags:任务清单 前言 现在xzy太弱了,而且他最近越来越弱了,天天被爆踩,天天被爆踩 题单不会在作业部落发布,所以可(yi)能(ding)会不及时更新 省选前的练习莫名其妙地成为了Noi ...

  8. 数据结构(莫队算法):国家集训队2010 小Z的袜子

    [题目描述] 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命…… 具体来说,小Z把这N只袜子从1到 ...

  9. 【国家集训队2010】小Z的袜子[莫队算法]

    [莫队算法][国家集训队2010]小Z的袜子 Description 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z再也无法忍受这恼人的找袜子过程, ...

随机推荐

  1. 53-Ubuntu-打包压缩-3-gzip压缩和解压缩介绍

    gzip tar与gzip命令结合可以实现文件打包和压缩. tar只负责打包文件,但不负责压缩. 用gzip压缩tar打包后的文件,其扩展名一般为xxx.tar.gz. 注:在Linux中,最常见的压 ...

  2. 【踩坑】IDEA 设置 JVM 参数

    1.可视化界面设置 Run->Edit Configuration... 然后设置 2.配置文件设置 打开 IDEA 安装目录,看到有一个 bin 目录,其中有两个 vmoptions 文件,需 ...

  3. erlang在windows下和虚拟机节点通信

    版权声明:博客将逐步迁移到 http://cwqqq.com https://blog.csdn.net/cwqcwk1/article/details/24738599 在Linux下部署erlan ...

  4. Elasticsearch索引别名使用

    背景 项目中使用的老的索引,由于数据冗余,会想影响性能.因此需要重新建立索引,但是这样必然需要更新服务中的索引名称,然后重新启动服务,可能会对服务的使用者产生一定的影响.因此,调研了Elasticse ...

  5. 前端 css 进阶

    内容目录: 1.css盒子模型 2.浮动 float 3.溢出 overflow 4.定位 position 5. z-index 6.opacity 1.css盒子模型 margin:用来调节盒子与 ...

  6. docker学习---搭建Docker LAMP环境

    1.环境 系统版本:CentOS Linux release 7.4.1708 docker版本:docker-ce-18.09 主机IP:192.168.121.121 2.载入MySQL和PHP镜 ...

  7. 深度探索C++对象模型读书笔记-第六章执行期语意学

    在函数中,编译器会帮助将析构函数(Destructor) 安插在相应的位置.对于函数中的局部对象,会将析构函数安插在对象的每一个离开点. 例如: 1: void Function(int a) { 2 ...

  8. 笔记41 Spring Web Flow——Demo

    订购披萨的应用整体比较比较复杂,现拿出其中一个简化版的流程:即用户访问首页,然后输入电话号(假定未注册)后跳转到注册页面,注册完成后跳转到配送区域检查页面,最后再跳转回首页.通过这个简单的Demo用来 ...

  9. Jvm之class文件的加载、初始化

    编写的java文件在要真正运行时,会首先被编译成 “.class"结尾的二进制文件,然后被虚拟机加载.那么在虚拟机中一个class文件要成为java实例,需要经历好几个步骤: 一.class ...

  10. 关于使用vue时的个人规范

    js文件: 公共功能文件:common_功能名.js 例:common_ajax.js 页面级功能文件(在不同页面复用):page_功能名.js 放置在html文件中加载的js文件命名:app_htm ...