点击右上角即可分享
微信分享提示
maoyiting
音乐播放器
闲言碎语
闲言碎语
maoyiting
谨防被此博文误导 QAQ
导航
首页
留言
关于
组成
随笔分类
Others 1
算法笔记 22
题目选做 6
2022年6月 1
2022年4月 1
2021年4月 2
2021年2月 1
2020年12月 7
2020年11月 1
2020年10月 2
2020年9月 5
2020年8月 4
2020年7月 3
2020年5月 1
2020年4月 1
更多
数学 10
字符串 2
筛法 2
二分图匹配 1
动态规划 5
线性代数 2
博弈论 2
多项式 1
数据结构 4
图论 2
概率与期望 1
Luogu
Dlstxdy
莱莱(可爱的妹子)
黄队
管理
文章
联系
「算法笔记」线段树优化建图
maoyiting 2020-10-03 15:37 0次浏览 3条评论 3915 字数 算法笔记
首页 / 正文 分享到 :

一、引入

先来看一道题:CF786B Legacy

题目大意:n 个点、q 次操作。每一种操作为以下三种类型中的一种:

  • 操作一:连一条 uv 的有向边,权值为 w

  • 操作二:对于所有 i[l,r] 连一条 ui 的有向边,权值为 w

  • 操作三:对于所有 i[l,r] 连一条 iu 的有向边,权值为 w

求从点 s 到其他点的最短路。1n,q105,1w109

考虑暴力建图。显然不能通过此题。

这时候就需要用线段树优化建图了。线段树优化建图就是利用线段树,减少连边数量,从而降低复杂度。

二、基本思想

先建一棵线段树。假如现在我们要从 8 号点向区间 [3,7] 的所有点连一条权值为 w 有向边。

https://img2020.cnblogs.com/blog/1859218/202010/1859218-20201003152331500-2047892066.png

那么怎么连边?把区间 [3,7] 拆成 [3,4][5,6][7,7] 然后分别连边。

就这样:(如下图所示。其中黑色普通边的边权为 0,粉色边的边权为 w。)

https://img2020.cnblogs.com/blog/1859218/202010/1859218-20201003152713737-813466449.png

原来我们要连 5 条边,现在只需要连 3 条边,也就是 log27 条边。

于是 O(n) 的边数就优化成了 O(logn)

那么操作三用和操作二类似的方法连边。从区间 [3,7] 的所有点向 8 号点连一条权值为 w 有向边:(其实就是边反了个方向)

https://img2020.cnblogs.com/blog/1859218/202010/1859218-20201003155545073-505477788.png

以上是操作二与操作三分开来考虑的情形,那么操作二与操作三相结合该怎么办呢?

考虑建两棵线段树,第一棵只连自上而下的边,第二棵只连自下而上的边。方便起见,我们把第一棵树称作“出树”,第二棵树称作“入树”。

初始时自上而下或自下而上地在每个节点与它的父亲之间连边。由于两棵线段树的叶子节点实际上是同一个点,因此要在它们互相之间连边权为 0 的边。初始时是这样的:

https://img2020.cnblogs.com/blog/1859218/202010/1859218-20201003175442575-1059055629.png

建树部分的代码:(代码中的 K 是一个常数,根据数据范围而定。建出树和入树也可以分别用两个函数实现,这样就用不到 K 了。)

void build(int p,int l,int r){
    if(l==r){a[l]=p;return ;}    //a: 记录叶子节点的编号 
    add(p,p<<1,0),add(p,p<<1|1,0);    //出树(从 p 向 p 的左右儿子连一条边权为 0 的边) 
    add((p<<1)+K,p+K,0),add((p<<1|1)+K,p+K,0);    //入树(从 p 的左右儿子向 p 连一条边权为 0 的边) 
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r); 
}
//主函数中: 
for(int i=1;i<=n;i++)
    add(a[i],a[i]+K,0),add(a[i]+K,a[i],0);     //两棵线段树的叶子节点之间连边 

接下来:

  • 对于操作一,就从入树的叶子节点向出树的叶子节点连边。

  • 对于操作二,就从入树的叶子节点向出树中的对应区间连边。

  • 对于操作三,就从入树中的对应区间向出树中的叶子节点连边。

举个栗子。比如现在我们要从 8 号点向区间 [3,7] 的所有点连一条权值为 w 有向边。那么就如图所示连边:(为了让图更清楚,图中把入树和出树叶子节点之间相连的边省略了。)

https://img2020.cnblogs.com/blog/1859218/202010/1859218-20201003180159361-1510386633.png

连边部分的代码:

void modify(int p,int l,int r,int lx,int rx,int v,int w){
    if(l>=lx&&r<=rx){    //如果当前区间被涵盖
        if(opt==2) add(v+K,p,w);    //对于操作二,就从入树的叶子节点向出树中的对应区间连边。
        else add(p+K,v,w);    //对于操作三,就从入树中的对应区间向出树中的叶子节点连边。
        return;
    }
    int mid=(l+r)/2;
    if(lx<=mid) modify(p<<1,l,mid,lx,rx,v,w);
    if(rx>mid) modify(p<<1|1,mid+1,r,lx,rx,v,w);
} 
//主函数中:
for(int i=1;i<=m;i++){
    scanf("%lld",&opt);
    if(opt==1) scanf("%lld%lld%lld",&x,&y,&z),add(a[x]+K,a[y],z);    //对于操作一,就从入树的叶子节点向出树的叶子节点连边。
    else{
        scanf("%lld%lld%lld%lld",&x,&l,&r,&w);
        modify(1,1,n,l,r,a[x],w);
    }
}

三、代码实现

CF786B Legacy 无注释完整代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e6+5,K=5e5;
int n,m,s,opt,x,y,z,l,r,w,a[N],cnt,hd[N],to[N],nxt[N],val[N],d[N];
bool v[N];
priority_queue<pair<int,int> >q;
void add(int x,int y,int z){
    to[++cnt]=y,nxt[cnt]=hd[x],hd[x]=cnt,val[cnt]=z;
}
void build(int p,int l,int r){
    if(l==r){a[l]=p;return ;}
    int mid=(l+r)/2;
    add(p,p<<1,0),add(p,p<<1|1,0);
    add((p<<1)+K,p+K,0),add((p<<1|1)+K,p+K,0);
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r); 
}
void modify(int p,int l,int r,int lx,int rx,int v,int w){
    if(l>=lx&&r<=rx){
        if(opt==2) add(v+K,p,w);
        else add(p+K,v,w);
        return;
    }
    int mid=(l+r)/2;
    if(lx<=mid) modify(p<<1,l,mid,lx,rx,v,w);
    if(rx>mid) modify(p<<1|1,mid+1,r,lx,rx,v,w);
} 
void dij(int s){
    memset(d,0x3f,sizeof(d)),d[s]=0;
    q.push(make_pair(0,s));
    while(q.size()){
        int x=q.top().second;q.pop();
        if(v[x]) continue;
        v[x]=1;
        for(int i=hd[x];i;i=nxt[i]){
            int y=to[i],z=val[i];
            if(d[y]>d[x]+z) d[y]=d[x]+z,q.push(make_pair(-d[y],y));
        }
    }
}
signed main(){
    scanf("%lld%lld%lld",&n,&m,&s),build(1,1,n);
    for(int i=1;i<=n;i++)
        add(a[i],a[i]+K,0),add(a[i]+K,a[i],0);
    for(int i=1;i<=m;i++){
        scanf("%lld",&opt);
        if(opt==1) scanf("%lld%lld%lld",&x,&y,&z),add(a[x]+K,a[y],z);
        else{
            scanf("%lld%lld%lld%lld",&x,&l,&r,&w);
            modify(1,1,n,l,r,a[x],w);
        }
    }
    dij(a[s]+K);
    for(int i=1;i<=n;i++)
        printf("%lld%c",d[a[i]]!=0x3f3f3f3f3f3f3f3fll?d[a[i]]:-1,i==n?'\n':' ');
    return 0;
}

 

上一篇 下一篇
发表评论
表情
发表评论 修改评论
Powered by vue on cnblogs