bzoj3073 [Pa2011]Journeys(线段树优化建图+堆优dijkstra)

Description

Seter建造了一个很大的星球,他准备建造N个国家和无数双向道路。N个国家很快建造好了,用1..N编号,但是他发现道路实在太多了,他要一条条建简直是不可能的!于是他以如下方式建造道路:(a,b),(c,d)表示,对于任意两个国家x,y,如果a<=x<=b,c<=y<=d,那么在xy之间建造一条道路。Seter保证一条道路不会修建两次,也保证不会有一个国家与自己之间有道路。
Seter好不容易建好了所有道路,他现在在位于P号的首都。Seter想知道P号国家到任意一个国家最少需要经过几条道路。当然,Seter保证P号国家能到任意一个国家。

注意:可能有重边

Input

第一行三个数N,M,P。N<=500000,M<=100000。
后M行,每行4个数A,B,C,D。1<=A<=B<=N,1<=C<=D<=N。

Output

N行,第i行表示P号国家到第i个国家最少需要经过几条路。显然第P行应该是0。

Sample Input

5 3 4
1 2 4 5
5 5 4 4
1 1 3 3

Sample Output

1
1
2
0
1



分析:
dalao’s blog

显然最短路问题
但是如果我们对于区间暴力连边,边数会高达 n2m n 2 m

于是可以线段树来优化:两棵线段树:进树,出树
进树:从父亲向儿子连边(边权为0),表示能达到该区间就能达到该区间的子区间
出树:从儿子向父亲连边(边权为0),表示能从该区间出发就能从该区间的父区间出发
两树之间:进树向出树的对应区间连边(边权为0),表示到达该区间后,还能从该区间继续出发

对于给出的边,从出树中找到对应区间,向新建的辅助点连边(边权为0),从辅助点向进树的对应区间连边(边权为1),由于是无向边,要连两次

例如5个节点中连边 [2,3]>[4,5] [ 2 , 3 ] − > [ 4 , 5 ]
这里写图片描述

之后dijsktra,答案就是进树的叶子节点距离

tip

蜜汁CE,鬼知道为什么。。。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>

using namespace std;

const int N=5000010;
const int M=30000005;
struct node{
    int y,nxt,v;
};
node way[M];
int st[N],tot=0,cnt,out[N],in[N];
int n,m,s;

struct point{
    int u,dis;
    point(int uu=0,int d=0) {
        u=uu;dis=d;
    }
    bool operator <(const point &b) const {
        return dis>b.dis;
    }
};
priority_queue<point> q;

void add(int u,int w,int z) {
    tot++;way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot;
}

struct Tree{
    struct po{int l,r,pos;};
    po t[2000005];

    void build(int bh,int l,int r,int opt) {
        t[bh].pos=++cnt;
        t[bh].l=l; t[bh].r=r;
        if (l==r) {
            if (opt==0) out[l]=cnt;    //每个编号对应的线段树上的点
            if (opt==1) in[l]=cnt; 
            return;
        }
        int mid=(l+r)>>1;
        build(bh<<1,l,mid,opt);
        build(bh<<1|1,mid+1,r,opt);
        if (opt==0)                    //出树(子连父)
        {
            add(t[bh<<1].pos,t[bh].pos,0);
            add(t[bh<<1|1].pos,t[bh].pos,0);    
        }
        else                           //入树(父连子) 
        {
            add(t[bh].pos,t[bh<<1].pos,0);
            add(t[bh].pos,t[bh<<1|1].pos,0);
        } 
    }   
    void change(int bh,int l,int r,int L,int R,int w,int opt) {
        if (l>=L&&r<=R) {
            if (opt==0) add(t[bh].pos,w,0);
            else add(w,t[bh].pos,1);
            return;
        }
        int mid=(l+r)>>1;
        if (L<=mid) change(bh<<1,l,mid,L,R,w,opt);
        if (R>mid) change(bh<<1|1,mid+1,r,L,R,w,opt);
    }
};
Tree t1,t2;

int dis[N];
bool vis[N];

void dij() {
    s=in[s];         //起点 
    memset(dis,0x33,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[s]=0; 
    q.push(point(s,0));
    while (!q.empty()) {
        point now=q.top(); q.pop();
        if (vis[now.u]) continue;
        vis[now.u]=1;
        int u=now.u;
        for (int i=st[u];i;i=way[i].nxt)
            if (dis[way[i].y]>dis[u]+way[i].v) {
                dis[way[i].y]=dis[u]+way[i].v;
                q.push(point(way[i].y,dis[way[i].y]));
            }
    }
    for (int i=1;i<=n;i++)
        printf("%d\n",dis[in[i]]);
}

int main() 
{
    scanf("%d%d%d",&n,&m,&s);
    int a,b,c,d;
    cnt=0;       //总结点数
    t1.build(1,1,n,0); t2.build(1,1,n,1);
    for (int i=1;i<=n;i++) add(in[i],out[i],0);        
    for (int i=1;i<=m;i++) {
        scanf("%d%d%d%d",&a,&b,&c,&d);
        t1.change(1,1,n,a,b,++cnt,0);       //出树
        t2.change(1,1,n,c,d,cnt,1);         //入树
        t1.change(1,1,n,c,d,++cnt,0);
        t2.change(1,1,n,a,b,cnt,1);
    } 
    dij();
    return 0;
}

重构了代码,减少了一些无用的变量(AC代码)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>

using namespace std;

const int N=5000010;
const int M=30000010;
struct node{
    int y,nxt,v;
};
node way[M];
int n,m,s,cnt=0,st[N],tot=0,pos[N];

void add(int u,int w,int z) {
    tot++;way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot;
}

void build(int bh,int l,int r,int opt) {
    if (l==r) {
        if (!opt) pos[l]=bh;
        else add(bh+4*n,bh,0);       //入树->出树 
        return;
    }
    int mid=(l+r)>>1;
    build(bh<<1,l,mid,opt);
    build(bh<<1|1,mid+1,r,opt);
    int lc=bh<<1,rc=bh<<1|1;
    if (!opt)                        //出树
    {
        add(lc,bh,0);
        add(rc,bh,0);
    }
    else
    {
        add(bh+4*n,lc+4*n,0);
        add(bh+4*n,rc+4*n,0);
    } 
}

void change(int bh,int l,int r,int L,int R,int w,int opt) {
    if (l>=L&&r<=R) {
        if (!opt) add(bh,w,0);
        else add(w,bh+4*n,1);
        return;
    }
    int mid=(l+r)>>1;
    if (L<=mid) change(bh<<1,l,mid,L,R,w,opt);
    if (R>mid) change(bh<<1|1,mid+1,r,L,R,w,opt);
}

struct point{
    int u,dis;
    point(int uu=0,int d=0) {
        u=uu;dis=d;
    }
    bool operator <(const point &b) const {
        return dis>b.dis;
    }
};
priority_queue<point> q;

int dis[N];
bool vis[N];

void dij() {
    s=pos[s];         //起点 
    memset(dis,0x33,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[s]=0; 
    q.push(point(s,0));
    while (!q.empty()) {
        point now=q.top(); q.pop();
        if (vis[now.u]) continue;
        vis[now.u]=1;
        int u=now.u;
        for (int i=st[u];i;i=way[i].nxt)
            if (dis[way[i].y]>dis[u]+way[i].v) {
                dis[way[i].y]=dis[u]+way[i].v;
                q.push(point(way[i].y,dis[way[i].y]));
            }
    }
    for (int i=1;i<=n;i++)
        printf("%d\n",dis[pos[i]]);
}

int main() {
    scanf("%d%d%d",&n,&m,&s);
    build(1,1,n,0); build(1,1,n,1);
    int a,b,c,d;
    cnt=n<<3;
    for (int i=1;i<=m;i++) {
        scanf("%d%d%d%d",&a,&b,&c,&d);
        change(1,1,n,a,b,++cnt,0); change(1,1,n,c,d,cnt,1);
        change(1,1,n,c,d,++cnt,0); change(1,1,n,a,b,cnt,1);
    }
    dij();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值