[xsy1129] flow [树链剖分和线段树一起优化网络流][我也不知道这是什么鬼标签]

题面

内部OJ

思路

考虑一个决策方案${x}$,$x_i$表示第$i$个点选不选,$f^k_i$表示点$i$的第$k$个父亲

那么可以得到总花费的表达式$ans=\sum V_i x_i - \sum max(x_i-min(x_{f^1_i},x_{f^2_i},x_{f^3_i},...x_{f^k_i}),0)\ast P_i$

优化一下表达方式:把收益和支出分开

$ans=\sum_{V_i>0} V_i - \sum_{V_i>0} V_i (1-x_i) - \sum_{V_i<0} (-V_i)x_i - \sum max(x_i-min(x_{f^1_i},x_{f^2_i},x_{f^3_i},...x_{f^k_i}),0)\ast P_i$

看着很长是不是?其实它很简洁:全部正收益,减掉没拿到的正收益,减掉拿了的负收益,减掉因为限制关系导致的负收益

我们发现后面三个$\sum$都是要和$x_i$相关的决策变量

观察【这里我也不知道怎么观察的,反正题解是这么观察的= =】可以知道,这三个地方都可以对应经典的最小割模型

【解释一下】经典最小割模型,就是指一对源点汇点,然后中间两列分开的节点集合{l},{r}

所有$(S,l)$和$(r,T)$的边都有值,$lr$中间的边代表限制关系

大概是这样的一个最小流,可以解决双向限制并行(也就大概相当于一个&)的最优化问题

这道题里面,我们发现运用这个技巧以后,前两个$\sum$的东西都很好解决了:

对于$V_i>0$的点,建立边$(S,i,V_i)$和$(i,T,0)$

对于$V_i<0$的点,建立边$(S,i,0)$和$(i,T,-V_i)$

这样可以很好的解决前两个限制,但是对于第三个限制怎么办呢?

我们发现我们实际上需要一个节点来代替$min(x_{f^1_i},x_{f^2_i},x_{f^3_i},...x_{f^k_i})$这一坨东西的功能

思考,一个节点$y_i$,如果代替了上面那个东西,那么意味着它小于任何一个$x_{f^k_i}$

那么就有限制关系$y_i \leq x_{f^k_i}$,我们可以对于这个限制建边$(y_i,x_{f^k_i},inf)$,使得不会出现$y_i\le x_{f^k_i}$的情况

【这段看起来可能有点复杂,但是实际上就是分步建立边、建立最小割的限制关系而已,一定要细细理解】

建图完成以后,我们发现这个复杂度好像不太够啊,暴力建边肯定炸了啊

没关系,我们用树剖+线段树辅助建边一下就好了

这东西的边数是$O(n\log ^2n)$级别的,可以接受【总理论复杂度?那是什么?网络流有这东西吗?】

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<queue>
#define ll long long
using namespace std;
inline int read(){
    int re=0,flag=1;char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(isdigit(ch)) re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
    return re*flag;
}
int n,v[100010],k[100010],p[100010];
namespace g{
    int first[100010],cnte=-1;
    void init(){memset(first,-1,sizeof(first));cnte=-1;}
    struct edge{
        int to,next,w;
    }a[6000010];
    inline void add(int u,int v,int w){
        a[++cnte]=(edge){v,first[u],w};first[u]=cnte;
        a[++cnte]=(edge){u,first[v],0};first[v]=cnte;
    }
    queue<int>q;int dep[100010],cur[100010];
    bool bfs(int s,int t){
        int i,u,v;
        for(i=s;i<=t;i++) dep[i]=-1,cur[i]=first[i];
        q.push(s);dep[s]=0;
        while(!q.empty()){
            u=q.front();q.pop();
            for(i=first[u];~i;i=a[i].next){
                v=a[i].to;if(!a[i].w||~dep[v]) continue;
                dep[v]=dep[u]+1;q.push(v);
            }
        }
        return ~dep[t];
    }
    int dfs(int u,int t,int lim){
        if(u==t||!lim) return lim;
        int i,v,f,flow=0;
        for(i=cur[u];~i;i=a[i].next){
            v=a[i].to;cur[u]=i;
            if(dep[v]==dep[u]+1&&(f=dfs(v,t,min(lim,a[i].w)))){
                a[i].w-=f;a[i^1].w+=f;
                flow+=f;lim-=f;
                if(!lim) return flow;
            }
        }
        return flow;
    }
    int dinic(int s,int t){
        int re=0;
        while(bfs(s,t)) re+=dfs(s,t,1e9);
        return re;
    }
}
int fa[1000010],dep[100010],siz[100010],son[100010],back[100010],dfn[100010],top[100010],clk;
namespace t{
    int first[100010],cnte=-1;
    void init(){memset(first,-1,sizeof(first));}
    struct edge{
        int to,next;
    }a[200010];
    inline void add(int u,int v){
        a[++cnte]=(edge){v,first[u]};first[u]=cnte;
        a[++cnte]=(edge){u,first[v]};first[v]=cnte;
    }
    void dfs1(int u,int f){
        int i,v;
        dep[u]=dep[f]+1;
        siz[u]=1;son[u]=0;
        for(i=first[u];~i;i=a[i].next){
            v=a[i].to;if(v==f) continue;
            dfs1(v,u);
            siz[u]+=siz[v];
            if(siz[son[u]]<siz[v]) son[u]=v;
        }
    }
    void dfs2(int u,int t){
        int i,v;
        top[u]=t;
        dfn[u]=++clk;back[clk]=u;
        if(son[u]) dfs2(son[u],t);
        for(i=first[u];~i;i=a[i].next){
            v=a[i].to;if(v==fa[u]||v==son[u]) continue;
            dfs2(v,v);
        }
    }
}
namespace seg{
    int seg[100010],cnt;
    void init(){cnt=n;}
    void build(int l,int r,int num){
        if(l==r){seg[num]=back[l];return;}
        int mid=(l+r)>>1;
        seg[num]=++cnt;
        build(l,mid,num<<1);g::add(seg[num],seg[num<<1],1e9);
        build(mid+1,r,num<<1|1);g::add(seg[num],seg[num<<1|1],1e9);
    }
    void add(int l,int r,int ql,int qr,int num,int from){
        assert(ql);
        if(l>=ql&&r<=qr){g::add(from,seg[num],1e9);return;}
        int mid=(l+r)>>1;
        if(mid>=ql) add(l,mid,ql,qr,num<<1,from);
        if(mid<qr) add(mid+1,r,ql,qr,num<<1|1,from);
    }
}
void link(int u,int tot){//树剖辅助建边
    if(!tot) return;
    int from=u+seg::cnt;
    u=fa[u];
    while(tot){
        if(tot>=dep[u]-dep[top[u]]+1){
            seg::add(1,n,dfn[top[u]],dfn[u],1,from);
            tot-=dep[u]-dep[top[u]]+1;
            u=fa[top[u]];
        }
        else{
            seg::add(1,n,dfn[u]-tot+1,dfn[u],1,from);
            break;
        }
    }
}
int main(){
    n=read();int i,S,T,ans=0;
    t::init();
    for(i=1;i<=n;i++){
        fa[i]=read();v[i]=read();k[i]=read();p[i]=read();
        if(fa[i]) t::add(fa[i],i);
    }
    t::dfs1(1,0);
    t::dfs2(1,1);
    g::init();
    seg::init();
    seg::build(1,n,1);
    S=0;T=seg::cnt+n+1;
    for(i=1;i<=n;i++){
        if(v[i]>=0){
            ans+=v[i];
            g::add(S,i,v[i]);
            g::add(i,T,0);
        }
        else{
            g::add(S,i,0);
            g::add(i,T,-v[i]);
        }
        g::add(i,i+seg::cnt,p[i]);
        link(i,k[i]);
    }
    printf("%d\n",ans-g::dinic(S,T));
}

原文地址:https://www.cnblogs.com/dedicatus545/p/10352349.html

时间: 2024-09-28 20:51:26

[xsy1129] flow [树链剖分和线段树一起优化网络流][我也不知道这是什么鬼标签]的相关文章

poj 3237 Tree(树链剖分,线段树)

Tree Time Limit: 5000MS   Memory Limit: 131072K Total Submissions: 7268   Accepted: 1969 Description You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edges are numbered 1 through N − 1. Each edge is associated with

bzoj 3626 [LNOI2014]LCA(离线处理+树链剖分,线段树)

3626: [LNOI2014]LCA Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1272  Solved: 451[Submit][Status][Discuss] Description 给出一个n个节点的有根树(编号为0到n-1,根节点为0).一个点的深度定义为这个节点到根的距离+1. 设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先. 有q次询问,每次询问给出l r z,求sigma_{l<=i<=r}dep[

bzoj 1036 [ZJOI2008]树的统计Count(树链剖分,线段树)

1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 10677  Solved: 4313[Submit][Status][Discuss] Description 一 棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值

HDU - 3966 Aragorn&#39;s Story(树链剖分入门+线段树)

HDU - 3966 Aragorn's Story Time Limit: 3000MS   Memory Limit: 32768KB   64bit IO Format: %I64d & %I64u Submit Status Description Our protagonist is the handsome human prince Aragorn comes from The Lord of the Rings. One day Aragorn finds a lot of ene

bzoj 3531 [Sdoi2014]旅行(树链剖分,线段树)

3531: [Sdoi2014]旅行 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 876  Solved: 446[Submit][Status][Discuss] Description S国有N个城市,编号从1到N.城市间用N-1条双向道路连接,满足 从一个城市出发可以到达其它所有城市.每个城市信仰不同的宗教,如飞天面条神教.隐形独角兽教.绝地教都是常见的信仰.为了方便,我们用不同的正整数代表 各种宗教,  S国的居民常常旅行.旅行时他们总

树链剖分处理+线段树解决问题 HDU 5029

http://acm.split.hdu.edu.cn/showproblem.php?pid=5029 题意:n个点的树,m次操作.每次操作输入L,R,V,表示在[L,R]这个区间加上V这个数字.比如[1,2]加上1,[1,3]加上1,那么1这个点就是{1,1},2也是{1,1},3是{1}.全部操作操作完之后,输出每个点中,最多的那个数字有几个.如果有相同的数字,就输出最小的那个数.比如{1,1,2,2},就输出1. 思路:树链剖分拍成链,然后通过找LCA,来离线预处理,然后最后通过离线暴力

[Bzoj4196] [NOI2015] 软件包管理器 [树链剖分,线段树]

题解摘要:树链剖分后用线段树区间查询修改,对于安装软件,将改点到根的路径全部变为1,对于卸载软件,将子树清空.注意边界,编号是从0开始的,容易漏掉树根. 第一次写树剖- 1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cstdlib> 5 #include <cstring> 6 #include <cmath> 7 #inclu

ACM-ICPC 2018 焦作赛区网络预赛 E. Jiu Yuan Wants to Eat (树链剖分-线性变换线段树)

树链剖分若不会的话可自行学习一下. 前两种操作是线性变换,模\(2^{64}\)可将线段树全部用unsigned long long 保存,另其自然溢出. 而取反操作比较不能直接处理,因为其模\(2^{64}\)的特殊性,可将其转化为线性变换. 显然 \[-x\equiv (2^{64}-1)*x (mod\ 2^{64})\] 因为\[!x = (2^{64}-1) -x \] 所以 \[ !x = (2^{64}-1) + (2^{64}-1)x\] #include<bits/stdc++

Luogu2542 AHOI2005 航线规划 树链剖分、线段树

传送门 看到删边不用想就是反着加边 先把删完边之后的图拆一个生成树出来,然后考虑非树边的影响.实际上非树边就是让树上的一条路径的权值从$1$变为了$0$,而每一个询问就是一条路径上的权值之和.使用树链剖分+线段树维护权值即可. 1 #include<bits/stdc++.h> 2 #define lch (now << 1) 3 #define rch (now << 1 | 1) 4 #define mid ((l + r) >> 1) 5 //This

cogs 1583. [POJ 3237] 树的维护 树链剖分套线段树

1583. [POJ 3237] 树的维护 ★★★★   输入文件:maintaintree.in   输出文件:maintaintree.out   简单对比时间限制:5 s   内存限制:128 MB [题目描述] 给你由N个结点组成的树.树的节点被编号为1到N,边被编号为1到N-1.每一条边有一个权值.然后你要在树上执行一系列指令.指令可以是如下三种之一: CHANGE i v:将第i条边的权值改成v. NEGATE a b:将点a到点b路径上所有边的权值变成其相反数. QUERY a b