CF786B Legacy 线段树优化建图

题目链接

读题发现,题目里有三种连边方式:

1.两点之间连一条有向边。

2.一个点与给定区间中的点连一条有向边。

3.给定区间中的点和一个点连一条有向边。

剩下的就是跑最短路。

两点之间连边很好处理,但是如何在区间之间连边?按平时链式前向星加边,最坏可能是n方的复杂度,建个边都要死。

这时就有线段树来优化一波。看到区间,还是可以往线段树的方向上去想一想。因为线段树的每个点都代表一段连续的区间。

可以建两棵线段树,一棵维护区间向外边的点连边,另一棵维护外边的点与区间中连边。用动态开点,保存左儿子和右儿子。

递归到叶子节点时,叶子节点的编号就是该点。所以一开始的根节点是n。最后回溯时将每个点与左右儿子都连上一条边权为0的边,这样连上某个线段树上的点就等同于某个点与区间中的点都连了一条边,时间复杂度nlog(n+m)(m次询问)。

按询问连边时就是线段树update操作改一下,最后跑最短路即可,注意要开long long

代码如下:

//思路是真的非常巧妙!! 
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+7;
const long long INF=0x3f3f3f3f3f3f3f3f;
struct node{
    int nxt,to,val;
}edge[maxn*20];
priority_queue< pair<long long,int> >q; 
int head[maxn],cnt;
bool vis[maxn];
int n,m,s;
int opt,u,v,w;
void add(int x,int y,int v){
    edge[++cnt].nxt=head[x];
    edge[cnt].to=y;
    edge[cnt].val=v;
    head[x]=cnt;
}
int num,rt1,rt2;
int lc[maxn],rc[maxn];
void build1(int &rt,int l,int r){
    if(l==r){
        rt=l;
        return;
    }
    rt=++num;
    int mid=(l+r)>>1;
    build1(lc[rt],l,mid);
    build1(rc[rt],mid+1,r);
    add(rt,lc[rt],0);
    add(rt,rc[rt],0);
}
void build2(int &rt,int l,int r){
    if(l==r){
        rt=l;
        return;
    }
    rt=++num;
    int mid=(l+r)>>1;
    build2(lc[rt],l,mid);
    build2(rc[rt],mid+1,r);
    add(lc[rt],rt,0);
    add(rc[rt],rt,0);
}
int l1,r1;
void update(int rt,int l,int r,int u,int v,int which){
    if(l1<=l&&r<=r1){
        which==2?add(u,rt,v):add(rt,u,v);//包含了当前区间,直接连边。 
        return;
    } 
    int mid=(l+r)>>1;
    if(l1<=mid) update(lc[rt],l,mid,u,v,which);
    if(r1>mid) update(rc[rt],mid+1,r,u,v,which); 
}
long long dis[maxn];
void dijkstra(int x){
    for(int i=1;i<=num;i++) dis[i]=INF;
    memset(vis,false,sizeof(vis));
    q.push(make_pair(0,x));
    dis[x]=0;
    while(q.size()!=0){
        int u=q.top().second;
        q.pop();
        if(vis[u]) continue;
        vis[u]=true;
        for(int i=head[u];i;i=edge[i].nxt){
            int v=edge[i].to;
            if(dis[v]>dis[u]+edge[i].val){
                dis[v]=dis[u]+edge[i].val;
                q.push(make_pair(-dis[v],v));
            }
        }
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&s);
    num=n;
    build1(rt1,1,n);
    build2(rt2,1,n);
    for(int i=1;i<=m;i++){
        scanf("%d",&opt);
        if(opt==1){
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
        }
        else{
            scanf("%d%d%d%d",&u,&l1,&r1,&w);
            update(opt==2?rt1:rt2,1,n,u,w,opt);
        }
    }
    dijkstra(s);
    for(int i=1;i<=n;i++){
        printf("%lld ",dis[i]<INF?dis[i]:-1);
    }
    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/LJB666/p/11389036.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值