[JZOJ1904] 【2010集训队出题】拯救Protoss的故乡

题目

题目大意

给你一个树形的网络,每条边从父亲流向儿子。根节点为原点,叶子节点流向汇点,容量为无穷大。
可以给一些边扩大容量,最多总共扩大\(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;
}

总结

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

原文地址:https://www.cnblogs.com/jz-597/p/11329599.html

时间: 2024-10-07 07:41:22

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

【2010集训队出题】小Z的袜子

Description 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命-- 具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L 尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬. 你的任务便是告诉小Z,他有多大的概率抽到两只颜色相同的袜子.当然,小Z希望这个概率尽量高,所以他可能会询问多个(L,R)以方便自己

【JZOJ1899】【2010集训队出题】剪枝

题目大意 给出一个有根树,\(1\)为根,若某个节点的儿子全是叶子,你可以将该节点的儿子全部剪掉,这样的操作可以进行多次.定义这棵树的价值为:将树上所有叶子按照\(dfs\)序排序后,所有叶子点权之和-相邻两叶子路径上点权最大值.现在你要通过剪枝使得这棵树价值最大. \(n\leq 100000\) 分析 设\(f_i\)表示\(i\)作为最后一个叶子时的最大价值.暴力枚举原树(没有剪枝)相邻的两个叶子,显然左链上每个点的\(f\)都可以转移到右链上,我们暴力处理出这条路径,分类讨论点权最大值在

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

题目 题目大意 给你个平面上的一堆点,问序列\({p_i}\)的个数. 满足\(y_{p_{i-1}}>y_{p_i}\)并且\(x_{p_i}\)在\(x_{p_i-1}\)和\(x_{p_i-2}\)之间. 正解 我不知道为什么我的树状数组打挂了--尽管不一定能AC,但是WA了-- 这题的正解有很多,最为传奇的,则是彭大爷的神仙解法. 显然这是个DP,而他抛弃了按照\(y\)从大到小排序的传统做法,反而是以\(x\)从小到大排序.将\({p_i}\)倒过来做.设\(f_{i,0/1}\)表示

JZOJ 1981. 【2011集训队出题】Digit

JZOJ 1981. [2011集训队出题]Digit Time Limits: 1000 ms Memory Limits: 128000 KB Description 在数学课上,小T又被老师发现上课睡觉了.为了向全班同学证明小T刚才没有好好听课,数学老师决定出一道题目刁难一下小T,如果小T答不出,那么-- 情节就按照俗套的路线发展下去了,小T显然无法解决这么复杂的问题,可怜的小T只能向你求助: 题目是这样的: 求一个满足条件的n位数A(不能有前导0),满足它的数字和为s1,并且,A*d的数

【JZOJ1914】【2011集训队出题】最短路

题目大意 给你一个带权无向图,满足图上任意一条边最多属于一个环,有\(q\)个询问,求\(u,v\)之间的最短路. \(n,q\leq 10000\) Solution 首先用Tarjan建一棵以\(1\)为根的搜索树,找出每个环,记录环的总长,将环内每个点\(u\)连向环内\(dfs\)序最小的点\(v\),边权为\(u\)到\(v\)的最短路,然后把不在环上的边照旧连上,这样我们就得到了一棵树. 现在要求\(a\)到\(b\)的最短路,我们考虑倍增,若两个点在一条链上,它们的最短路就是树上的

圆方树小结

圆方树 jzoj 1914. [2011集训队出题]最短路 这是道圆方树+倍增LCA裸题. 圆方树,顾名思义,就是圆点和方点所组成的树. 而方点就是一个圆的根,一般都是\(dfs\)时第一个到这个圆的那个位置,然后另附一个点当做方点.然后圆所组成的点都连向方点. 而对于这种圆方边的边权,则为它到根的最近值. 从而将一个仙人掌转成了一棵树. 然后对于这棵树,我们就可以用倍增来求出两两点之间的最短路了. 注意的是,对于最后走到的位置,我们要看看它是从上面绕近还是直接从下面走更优!!! 就这样子了.

[国家集训队2010]小Z的袜子

★★★   输入文件:hose.in   输出文件:hose.out   简单对比 时间限制:1 s   内存限制:512 MB [题目描述] 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命…… 具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L 尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬. 你的任务便

洛谷 1775. [国家集训队2010]小Z的袜子

1775. [国家集训队2010]小Z的袜子 ★★★   输入文件:hose.in   输出文件:hose.out   简单对比时间限制:1 s   内存限制:512 MB [题目描述] 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命…… 具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L 尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,

AC日记——[国家集训队2010]小Z的袜子 cogs 1775

[国家集训队2010]小Z的袜子 思路: 传说中的莫队算法(优雅的暴力): 莫队算法是一个离线的区间询问算法: 如果我们知道[l,r], 那么,我们就能O(1)的时间求出(l-1,r),(l+1,r),(l,r-1),(l,r+1); 莫队算法怎么保证时间呢? 把询问排序; 然后进行暴力; 但是这样仍然需要很长很长的时间; 所以,我们引入一个根号方法,分块; 把区间的点分块; 然后每个询问的l,r按l所属的块为第一关键字,l,r为第二第三; 排序完后,就可以保证复杂度是O(n*sqrt(n));