Legacy (线段树优化建图)

题目链接:Legacy - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题解:

考虑题目中一个点向区间连边,如真的对区间中的每一点分别连边后跑最短路,时间空间都要炸。

因为是一个点向区间连边,考虑线段树。

1到n构造两颗区间线段数

 观察上图(从网上搬的)

两颗线段树,一颗入树父亲向儿子连边,用来优化指向叶子的边(左边的树)一颗出树儿子向父亲连边用来优化从叶子指出的点(右边的树)两棵树完全相同。

每个叶子为表示一个星球(可以理解为拆点处理入边和出边),对应的星球之间连一条边,边权为0,因为一个点到自己不花费代价(图中黄色的边)

对于题中给出的操作,操作一可以与操作二等价(也可以与操作三等价)理解为一个点可以去一个区间。

操作二从第二个树上的叶子v向第一颗树上对应的区间连边(与线段树操作相同)

操作三从第一个树上的叶子v向第二颗颗树上对应的区间连边(与上图不太相同,可以自己画图理解)

最后从s点跑最短路

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
const int M=4e6+10;
#define int long long//数据比较大,会爆int
int tot,n,q,s,cnt,root,nxt[M],go[M],hd[M],dx[M],vis[M],dis[M],jz[M],ans,mi,mx;
void add(int x,int y,int w)
{
    nxt[++tot]=hd[x],go[tot]=y,jz[tot]=w,hd[x]=tot;
    return ;
}
void build(int x,int l,int r,int lx,int lim)//用lim将第一颗树和第二棵树一起处理
{
    if(l==r)
    {
        x=x+lim;
        if(x>mx)mx=x;
        dx[++cnt]=x;
        return ;
    }
    int mid=(l+r)>>1;
    if(lx==1)add(x+lim,x*2+lim,0),add(x+lim,x*2+1+lim,0);
    if(lx==2)add(x*2+lim,x+lim,0),add(x*2+1+lim,x+lim,0);
    build(x*2,l,mid,lx,lim);
    build(x*2+1,mid+1,r,lx,lim);
}
void search(int x,int l,int r,int lt,int rt,int v,int w,int lx)
{
    if(lt<=l&&rt>=r)
    {
        if(lx==1)add(v,x,w);
        else add(x+root-1,v,w);
        return ;
    }
    int mid=(l+r)>>1;
    if(lt<=mid)search(x*2,l,mid,lt,rt,v,w,lx);
    if(rt>mid)search(x*2+1,mid+1,r,lt,rt,v,w,lx);
    return ;
}
struct node
{
    int id,zhi;
    bool operator<(const node& a)const {return zhi>a.zhi;}
    node(int x,int y){id=x,zhi=y;}
};
void dij(int x)
{
    memset(dis,0x7f,sizeof(dis));
    priority_queue<node> q;
    dis[x]=0;
    q.push(node(x,0));
    while(!q.empty())
    {
        int u=q.top().id;q.pop();
        if(vis[u])continue;
        vis[u]=1;
        for(int i=hd[u];i;i=nxt[i])
        {
            int v=go[i];
            if(dis[v]>dis[u]+jz[i])
            {
                dis[v]=dis[u]+jz[i];
                if(!vis[v])q.push(node(v,dis[v]));
            }
        }
    }
    return ; 
}
signed main()
{
    scanf("%lld%lld%lld",&n,&q,&s);
    build(1,1,n,1,0);
    root=mx+1;//第二颗树的root
    build(1,1,n,2,mx);//从第二棵树的点表示为mx+1,mx+2,mx+3而不是mx,2*mx,2*mx+1节约空间不然会MLE。
    mx=0,mi=1e9;
    for(int i=1;i*2<=cnt;i++)//dx存叶子,考虑线段树过程以及两颗线段树相同,可以得出第i(i<=cnt/2)或i-cnt/2(i>cnt/2)个的对应线段树节点为i,
    {
        if(dx[i]>mx)mx=dx[i];
        if(dx[i]<mi)mi=dx[i];
        add(dx[i],dx[i+cnt/2],0);
        add(dx[i+cnt/2],dx[i],0);//黄色边,即自己向自己连边
    }    
    for(int i=1;i<=q;i++)
    {
        int lx,l,r,v,w;;scanf("%lld",&lx);
        if(lx==1)
        {
            scanf("%lld%lld%lld",&v,&l,&w);r=l;
            search(1,1,n,l,r,dx[v+cnt/2],w,1);
        }
        if(lx==2)
        {
            scanf("%lld%lld%lld%lld",&v,&l,&r,&w);
            search(1,1,n,l,r,dx[v+cnt/2],w,1);            
        }
        if(lx==3)
        {
            scanf("%lld%lld%lld%lld",&v,&l,&r,&w);
            search(1,1,n,l,r,dx[v],w,2);                
        }
    }
    dij(dx[s]);//最短路
    for(int i=1;i<=n;i++)
    {
        if(dis[dx[i]]==9187201950435737471) dis[dx[i]]=-1;//特殊处理不连通
        printf("%lld ",dis[dx[i]]);dis存的是线段树节点
    }
    return 0;
}

别人的建图

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); 
}

提前计算第一颗树的最大值,计为K.

对于i存i的对应线段树节点。

其他差别不大

posted @ 2024-01-25 19:49  storms11  阅读(9)  评论(0编辑  收藏  举报