zoukankan      html  css  js  c++  java
  • [线段树系列] 线段树优化建图

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

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

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

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

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

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

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

    我们来看这一道题: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。

  • 相关阅读:
    Muddy Fields
    LightOJ 1321
    LightOJ 1085
    LightOJ 1278
    LightOJ 1341
    LightOJ 1340
    vijos 1426 背包+hash
    vijos 1071 01背包+输出路径
    vijos 1907 DP+滚动数组
    vijos 1037 背包+标记
  • 原文地址:https://www.cnblogs.com/light-house/p/11761163.html
Copyright © 2011-2022 走看看