786B - Legacy(线段树 + 最短路)线段树优化建图

题意:

就是给定一张n nn个点的图,求源点s ss到每个点的单源最短路。
这张图共有q组边,连边方式有3种:

a→b ,边权为w的单向边;
a→[l,r] ,即a到连续区间[l,r]中的每一个点都有一条边权为w的边。
[l,r]→a,即连续区间[l,r] 中的每一个点都有一条到a 边权为w 的边。
注意数据范围n,q≤10^5.

题解

如果暴力连边(将一对多或者多对一的连边拆开分别连边),那么最差情况下,我们的边数将达到O(nq) 级别,连边就直接超出能承受的范围——更不要说再跑最短路了。因此我们考虑如何使用一些数据结构把这些一对多的情况压缩成少数的一条边,使每一个连边操作都是O(1) O(1)的。
注意到所有“一对多”和“多对一“中“多”都是连续区间,回想我们平时处理连续区间的时候,最经常采用的一个数据结构就是线段树了。于是我们把线段树抓两棵过来放在那边。

那我们怎么用线段树呢?

我们在做区间加法的时候,遇到区间问题,我们都是往下查点,直到以这个点为根节点的子树完全包含于我们需要的区间内的时候,直接返回这个点的值——那么我们也可以采用同样的思想:如果我们把线段树的叶节点当做是原图的点,那么对于以这个点为根节点的子树的所有叶节点如果都在范围内,那么我们是不是可以直接在这个子树的根节点的地方连边呢?这样,对于这样一个子区间,我们只需要1条边。至于我们给出的一段区间会被拆成几个小区间,根据线段树的基本知识,我们知道它是常数级别的,因此我们连边就全部变成了O(1)的。

慢着,我们刚才说连边连在了线段树的中间,可是原图的点是叶节点,

那么我们怎么才能走到中间的那些点呢?

于是我们就只好将每一个点向父节点连边,边权为0。这样就解决问题了?
并没有。因为我们连完边要跑最短路啊,如果我们都向父节点连边,那么中间代表区间的点又应该连向哪里呢?难道连向叶节点吗?这样只能解决多对一的问题,而一对多却不能解决。同样,如果由父节点连向儿子结点,也是不对的。
那么我们就开2 棵线段树吧!我们令一颗叫做“入”树(intree),一棵叫做“出”树(outree),其中intree内部从下往上连0权边,outree内部从上往下连边0权边,那么我们对应的连边操作就是:

对于第一种,我们直接将intree对应u的叶节点连向outree对应v的点。
对于第二种,我们将intree对应a的叶节点连向一个或少数几个outree对应的代表区间的结点。
对于第三种,我们将一个或少数几个intree对应的代表区间的结点连向outree对应a的叶节点。
特别的,因为我们对于每个点在intree和outree都有直接对应的结点,他们实际上表示的是同一个东西,所以我们在intree和outree的叶节点之间对应的连上无向边。
最后,我们只需要跑一遍从intree的对应s结点跑一遍到outree对应所有结点的最短路即可。

时间复杂度:O((n+q)log2n+最短路时间复杂度) 
?
空间复杂度:O(n) 。

(简单来说就是将对应区间(建图)内的点的权值赋为0,以区间对应的线段树上的结点作为一个点与对应点建图(区间对应的根结点与点建图),然后就是跑一遍最短路就行)

C++代码

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
#define lson(x) ((x)<<1)
#define rson(x) ((x)<<1|1)
const ll MAXN=100010;
const ll MAXM=1000010;
long long INF;

struct node{//segment_tree
    ll id;
    ll l,r;
}intree[MAXN<<2],outree[MAXN<<2];

//两种写法均可
//struct edge{//graph::edge
//    ll v;
//    long long w;
//    edge *nxt;
//    edge(){
//        v=w=0;
//        nxt=NULL;
//    }
//}*head[MAXM];

struct edge{
    ll v;
    long long w;
    ll nxt;
}e[MAXM<<2]; //记得开大,不然re

struct road{//dijkstra
    ll i;
    long long dis;
    bool operator<(const road &rhs)const{
        return dis>rhs.dis;
    }
};
priority_queue<road>q;
ll n,m,s;
ll innum[MAXN],ounum[MAXN];
long long d[MAXM];
ll vis[MAXM],cnt=0;
ll tot = 0;
ll head[MAXM<<2];
void adde(ll u,ll v,long long w){
//    edge *p=new edge;
//    p->v=v;p->w=w;
//    p->nxt=head[u];
//    head[u]=p;
tot++;
e[tot].v = v;
e[tot].w = w;
e[tot].nxt = head[u];
head[u] = tot;
}

ll inbuild(ll o,ll l,ll r){
    intree[o].l=l;intree[o].r=r;intree[o].id=cnt++;
    if(l==r)
        return innum[l]=intree[o].id;
    ll mid=(l+r)>>1;
    adde(inbuild(lson(o),l,mid),intree[o].id,0);
    adde(inbuild(rson(o),mid+1,r),intree[o].id,0);
    return intree[o].id;
}
ll oubuild(ll o,ll l,ll r){
    outree[o].l=l;outree[o].r=r;outree[o].id=cnt++;
    if(l==r)
        return ounum[l]=outree[o].id;
    ll mid=(l+r)>>1;
    adde(outree[o].id,oubuild(lson(o),l,mid),0);
    adde(outree[o].id,oubuild(rson(o),mid+1,r),0);
    return outree[o].id;
}
void inmodify(ll o,ll l,ll r,ll fa,long long w){
    if(intree[o].l>=l&&intree[o].r<=r){
        adde(intree[o].id,fa,w);
        return;
    }
    ll mid=(intree[o].l+intree[o].r)>>1;
    if(r<=mid)
        inmodify(lson(o),l,r,fa,w);
    else if(l>=mid+1)
        inmodify(rson(o),l,r,fa,w);
    else{
        inmodify(lson(o),l,mid,fa,w);
        inmodify(rson(o),mid+1,r,fa,w);
    }
}
void oumodify(ll o,ll l,ll r,ll fa,long long w){
    if(outree[o].l>=l&&outree[o].r<=r){
        adde(fa,outree[o].id,w);
        return;
    }
    ll mid=(outree[o].l+outree[o].r)>>1;
    if(r<=mid)
        oumodify(lson(o),l,r,fa,w);
    else if(l>=mid+1)
        oumodify(rson(o),l,r,fa,w);
    else{
        oumodify(lson(o),l,mid,fa,w);
        oumodify(rson(o),mid+1,r,fa,w);
    }
}

void sssp(){
    memset(d,0x7f,sizeof(d));INF=d[0];
    q.push((road){innum[s],0});d[innum[s]]=0;
    while(!q.empty()){
        road t=q.top();q.pop();
        if(d[t.i]!=t.dis)
            continue;
        for(ll i=head[t.i];i!=NULL;i=e[i].nxt){
            if(t.dis+e[i].w<d[e[i].v]){
                ll v=e[i].v;
                d[v]=t.dis+e[i].w;
                q.push((road){v,d[v]});
            }
        }
    }
} 

int main(){
    scanf("%lld%lld%lld",&n,&m,&s);
    inbuild(1,1,n);oubuild(1,1,n);
    for(ll i=1;i<=n;i++){
        adde(innum[i],ounum[i],0);
        adde(ounum[i],innum[i],0);
    }
    while(m--){
        ll opt,v,l,r,w;scanf("%lld",&opt);
        switch(opt){
            case 1:{
                scanf("%lld%lld%lld",&l,&v,&w);
                adde(innum[l],ounum[v],w);
                break;
            }
            case 2:{
                scanf("%lld%lld%lld%lld",&v,&l,&r,&w);
                oumodify(1,l,r,innum[v],w);
                break;
            }
            case 3:{
                scanf("%lld%lld%lld%lld",&v,&l,&r,&w);
                inmodify(1,l,r,ounum[v],w);
                break;
            }
        }
    }
    sssp();
    for(ll i=1;i<=n;i++)
        if(d[ounum[i]]!=INF)
            printf("%lld%c",d[ounum[i]]," \n"[i==n]);
        else
            printf("-1%c"," \n"[i==n]);
    return 0;
}

原文地址:https://www.cnblogs.com/DWVictor/p/11203343.html

时间: 2024-10-26 17:55:32

786B - Legacy(线段树 + 最短路)线段树优化建图的相关文章

codeforces786B Legacy 线段树优化建图

网址:https://codeforces.com/problemset/problem/786/B 题意: 给出$n$个城市和三种路径:$u$向$v$连一条带权无向边:$[l,r]$向$v$连一条带权无向边:$u$向$[l,r]$连一条带权无向边,给出一个起点$s$,求它到其他点的最短路径,如果不能到达,输出$-1$. 题解: 现在有三种操作:点对点连边,点对线段连边,线段对点连边.且点数多达$1e5$,所以就要建立线段树优化建图.建立入度树(就是边的终点)时,需要连上父节点往子节点的点且边权

CF786B Legacy 线段树优化建图

问题描述 CF786B LG-CF786B 题解 线段树优化建图 线段树的一个区间结点代表 \([l,r]\) 区间点. 然后建立区间点的时候就在线段树上建边,有效减少点的个数,从而提高时空效率. 优质题解传送门 \(\mathrm{Code}\) #include<bits/stdc++.h> using namespace std; #define int long long template <typename Tp> void read(Tp &x){ x=0;ch

[XSY 1129] flow 最小割 树链剖分 线段树优化建图

题意 给定一张 $N$ 个点的有根树, $1$ 为树的根. 每个点有点权 $V[i]$ . 若 $V[i] > 0$ , 则可以获得 $V[i]$ 的收益; 若 $V[i] < 0$ , 则需要付出 $V[i]$ 的代价. 如果选择了某个点 $x$ , 且它的连续的 $K$ 个祖先中存在一个没有选, 则需要付出 $P_x$ 的代价. 最大化总收益. 分析 将 树链剖分 + zkw线段树 嵌入网络中, 跑最小割. 实现 #include <cstdio> #include <c

线段树优化建图

建图时,当边数非常的多,连边就需要线段树优化复杂度.将 O(n2) 优化成 O(nlogn). 图来自生哥,非常感谢他. ? 2018/2/23 18:51:00 就这个样子 ? 2018/2/23 18:51:10 绿边容量INF ? 2018/2/23 18:51:17 红边容量1 ? 2018/2/23 18:51:39 蓝边是个节点 代码实现待续-- 原文地址:https://www.cnblogs.com/milky-w/p/8463205.html

[bzoj3932][CQOI2015]任务查询系统-题解[主席树][权值线段树]

Description 最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分.超级计算机中的 任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第Ei秒后结束(第Si秒和Ei秒任务也在运行 ),其优先级为Pi.同一时间可能有多个任务同时执行,它们的优先级可能相同,也可能不同.调度系统会经常向 查询系统询问,第Xi秒正在运行的任务中,优先级最小的Ki个任务(即将任务按照优先级从小到大排序后取前Ki个 )的优先级之和是多少.特别的,如

Aizu 2450 Do use segment tree 树链剖分+线段树

Do use segment tree Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.bnuoj.com/v3/problem_show.php?pid=39566 Description Given a tree with n (1 ≤ n ≤ 200,000) nodes and a list of q (1 ≤ q ≤ 100,000) queries, process the queries in order and out

Hdu 3966 Aragorn&#39;s Story (树链剖分 + 线段树区间更新)

题目链接: Hdu 3966 Aragorn's Story 题目描述: 给出一个树,每个节点都有一个权值,有三种操作: 1:( I, i, j, x ) 从i到j的路径上经过的节点全部都加上x: 2:( D, i, j, x ) 从i到j的路径上经过的节点全部都减去x: 3:(Q, x) 查询节点x的权值为多少? 解题思路: 可以用树链剖分对节点进行hash,然后用线段树维护(修改,查询),数据范围比较大,要对线段树进行区间更新 1 #include <cstdio> 2 #include

hdu 5412 CRB and Queries(线段树套笛卡尔树 - 动态区间第k大)

题目链接:hdu 5412 CRB and Queries 首先对所有出现过的值排序,建立线段树,每个线段树的节点是一棵笛卡尔树,笛卡尔树记录区间下标值. #include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> using namespace std; #define lson(x) (x<<1) #define rson(x) ((x<<

hdu5029 树链剖分 + 线段树

将树映射在线段上进行操作 然后每个 重链变成一个连续的区间 #include <iostream> #include <cstdio> #include <string.h> #include <vector> #include <algorithm> #pragma comment(linker,"/STACk:10240000,10240000") using namespace std; const int maxn=1