• [线段树系列] 线段树优化建图


    这一篇讲线段树优化建图。

    发现网上关于线段树优化建图的博客很少而且讲的不是很详细,很多人会看得比较懵。

    于是原本这一篇打算讲树链剖分的就改成讲优化建图了。

    前置知识:动态开点线段树

    看到标题你可能会感觉奇怪,线段树和建图有什么关系?

    事实上,线段树优化建图就是利用两棵线段树,减少连边数量,达到降低复杂度的目的。

    听起来好像很神奇,其实实现非常简单。

    我们来看这一道题:CF786B-Legacy

    题目描述比较长,我就不打出来了,这里给出题目概述:

    有n个点,q个询问,每次询问给出一个操作。

    操作1:1 u v w,从u向v连一条权值为w的有向边

    操作2:2 u l r w,从u向区间[l,r]的所有点连一条权值为w的有向边

    操作3:3 u l r w,从区间[l,r]的所有点连一条权值为w的有向边

    连完边后跑一遍最短路就好了。

    首先考虑暴力连边,复杂度肯定是O(n^2)的,显然不行。

    然后我们看到了“区间”,“[l,r]”这种东西,肯定就会往数据结构上面想。

    看到博客的标题就明白,肯定是用线段树解决了。废话

    接下来讲实现。

    我们考虑用两棵线段树来搞,建两棵线段树,一棵处理入边,一棵处理出边。

    方便起见,我们下文称其为入树和出树。

    开始我们让父亲和儿子连边,然后我们再让入树和出树的叶子节点之间连上边权为0的边。

    建出来的图大概长这样:

    这图画得累死我了,画图真难用

    还是看不懂的就看代码吧:

    void buildOut(int &o,int l,int r){//建出树
        if(l==r){
            o=l;return;//已经是子节点,直接赋值 
        }
       o=++ncnt; int mid=(l+r)>>1; buildOut(lc[o],l,mid);buildOut(rc[o],mid+1,r); addedge(o,lc[o],0);//从o向o的左右子树连一条权值为0的有向边 addedge(o,rc[o],0); }
    void buildIn(int &o,int l,int r){//建入树
        if(l==r){
            o=l;return;//已经是子节点,直接赋值
        }
        o=++ncnt;//开新点
        int mid=(l+r)>>1;
        buildIn(lc[o],l,mid);buildIn(rc[o],mid+1,r);//递归建树
        addedge(lc[o],o,0);//从o的左右子树向o连一条权值为0的有向边 
        addedge(rc[o],o,0);
    }

    至此,线段树就建好了。

    现在我们需要用update操作来连边。

    我们首先定义修改区间为[L,R],我们有两种操作( 2和3 )。

    类似区间修改操作,如果当前区间被[L,R]涵盖,我们就连边。

    注意根据操作要求连边,不要连反了。

    给出这一部分的代码:

    void update(int o,int l,int r,int f,int val,short type){
        if(L<=l && R>=r){//被涵盖
            type==2?addedge(f,o,val):addedge(o,f,val);//如果是操作2就往区间连边,如果是操作3就往点连边
            return;
        }
        int mid=(l+r)>>1;
        if(L<=mid)update(lc[o],l,mid,f,val,type);
        if(R>mid)update(rc[o],mid+1,r,f,val,type);//递归连边
    }

    接下来写一个最短路,由于题目说明了1<=w<=1e9,所以我们选择dijkstra算法求最短路。

    不清楚堆优化的dijkstra算法的朋友可以找相关博客学习。

    我这里就不再赘述,现在给出最后的程序。

    #include<bits/stdc++.h>
    #define N 100010
    #define M 300010
    #define LOG 20
    typedef int mainint;
    #define int long long
    using namespace std;
    int head[M],lc[M*LOG],rc[M*LOG],tot,ncnt;
    int n,m,s,rt1,rt2;
    struct Edge{
        int nxt,to,val;
        #define nxt(x) e[x].nxt
        #define to(x) e[x].to
        #define val(x) e[x].val
    }e[N*LOG];
    inline void addedge(int f,int t,int val){
        nxt(++tot)=head[f];to(tot)=t;val(tot)=val;head[f]=tot;
    }
    void buildOut(int &o,int l,int r){//建出树 
        if(l==r){
            o=l;return;//已经是子节点,直接赋值 
        }o=++ncnt;
        int mid=(l+r)>>1;
        buildOut(lc[o],l,mid);buildOut(rc[o],mid+1,r);
        addedge(o,lc[o],0);//从o向o的左右子树连一条权值为0的有向边
        addedge(o,rc[o],0); 
    }
    void buildIn(int &o,int l,int r){//建入树 
        if(l==r){
            o=l;return;
        }
        o=++ncnt;
        int mid=(l+r)>>1;
        buildIn(lc[o],l,mid);buildIn(rc[o],mid+1,r);
        addedge(lc[o],o,0);//从o向o的左右子树连一条权值为0的有向边 
        addedge(rc[o],o,0);
    }
    int L,R;
    void update(int o,int l,int r,int f,int val,short type){
        if(L<=l && R>=r){
            type==2?addedge(f,o,val):addedge(o,f,val);
            return;
        }
        int mid=(l+r)>>1;
        if(L<=mid)update(lc[o],l,mid,f,val,type);
        if(R>mid)update(rc[o],mid+1,r,f,val,type);
    }
    const int inf=0x7fffffffffffffff;
    int dis[M];
    priority_queue< pair<int,int> > q;
    int vis[M];
    void dijkstra(int s){
        for(int i=1;i<=M;i++)dis[i]=inf,vis[i]=0;
        dis[s]=0;q.push(make_pair(0,s));
        while(q.size()){
            int x=q.top().second;q.pop();
            if(vis[x])continue;
            vis[x]=1;
            for(int i=head[x];i;i=nxt(i)){
                int y=to(i),z=val(i);
                if(!vis[y]&&dis[y]>dis[x]+z){
                    dis[y]=dis[x]+z;
                    q.push(make_pair(-dis[y],y));
                }
            }
        }
    }
    inline int read(){
        int data=0,w=1;char ch=0;
        while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar();
        if(ch=='-')w=-1,ch=getchar();
        while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar();
        return data*w;
    }
    mainint main(){
        n=read();m=read();s=read();
        ncnt=n;//建边要求,线段树节点从n+1开始编号 
        buildOut(rt1,1,n);buildIn(rt2,1,n);
        while(m--){
            int opt,f,t,val;
            opt=read();
            if(opt==1){
                f=read();t=read();val=read();
                addedge(f,t,val);//上面对叶子节点已经处理了,直接连边 
            }else{
                f=read();L=read();R=read();val=read();
                update(opt==2?rt1:rt2,1,n,f,val,opt);
            }
        }
        dijkstra(s);
        for(int i=1;i<=n;i++)
            printf("%lld ",dis[i]<inf?dis[i]:-1);
        return 0;
    }

    注意我用了#define int long long,这其实不是一个好习惯,只是我比较懒

    那么这篇博客到这里就结束了,线段树系列将停更一段时间( 很短 )。

    接下来我会更新一些其它数据结构、算法还有图论的博客。

    撰文不易,希望能帮到各位。求点赞求关注QuQ。

  • 相关阅读:
    记一次数据库查询超时的原因 Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. The statement has been terminated Felix
    RSA 算法流程 Felix
    linux shell 比较文件相同部分comm命令和不同部分diff命令 按行读取 文件
    jekins vue 发布脚本 shell
    android 自动化测试参考资料(待验证)
    centos7 redis安装
    技术比赛之转件存储设计
    YD设计架构
    LS项目相关图形
    关于公司开放平台的初期计划
  • 原文地址:https://www.cnblogs.com/light-house/p/11761163.html
Copyright © 2020-2023  润新知