【XSY2434】【CF787D】遗产(线段树+dijkstra)

\(Description\)

\(Rick\)和他的同事们研究出了一种新的有关放射的公式,于是许多坏人就在追赶他们。所以\(Rick\)希望在被坏人抓住之前把遗产给\(Morty\)。

在他们的宇宙里总共有\(n\)颗行星,每颗行星有它自己的编号(编号为\(1\)到\(n\))。\(Rick\)所在的行星的编号是\(s\)(地球),但是他不知道\(Morty\)在哪?总所周知,\(Rick\)有一门能打开奇妙入口的枪。在这把枪的帮助下,他能打开一扇单向门去往任意一个星球(包括那把枪自己所在的星球),但是这玩意是有限制的,因为\(Rick\)用的是这玩意的免费试用版。

一般而言,他不能用这把枪打开任意一扇单向的门。但是有\(q\)个套餐在它的官网上售卖。每一次你购买了这个套餐,你就能也仅仅能使用它一次,但是你可以重复购买(如果你觉得需要多次使用的话)。

网站上的套餐有以下三种类型:

1.打开一扇从\(v\)到\(u\)的门

2.打开一扇从\(v\)到\([l,r]\)之间任何一个的门

3.打开一扇从\([l,r]\)到\(v\)之间任何一个的门

\(Rick\)不知道\(Morty\)在哪?但是\(Unity\)准备告诉他。于是\(Rick\)就要准备好一切。

因为\(Rick\)的预算不多,所以他想知道从他的星球出发,到达每一个星球的最少花费是多少,如果到达不了,就输出\(-1\).


\(Input\)

第一行输入包括三个正整数\(n,q,s(1<=n,q<=10^5,1<=s<=n)\),\(n\)表示行星的个数,\(q\)表示计划的总数,\(s\)表示地球的编号。

接下来\(q\)行每行包括一个计划。每一行的第一个数字为\(t(1<=t<=3)\),表示该计划的类型。如果\(t=1\)就跟着输出\(v,u\)和\(w\),\(w\)表示这个计划的花费。其他的就输入\(v,l,r\)和\(w\)。 \((1<=v,u<=n,1<=l<=r<=n,1<=w<=10^9)\)


\(Output\)

输出一行包括\(n\)个数,\(a_{i}\)表示从地球到\(i\)这个星球的最小花费,如果到达不了则输出\(-1\)。


\(Sample\) \(Input\)

样例输入1:

3 5 1
2 3 2 3 17
2 3 2 2 16
2 2 2 3 3
3 3 1 1 12
1 3 3 17

样例输入2:

4 3 1
3 4 1 3 12
2 2 3 4 10
1 2 4 16


\(Sample\) \(Output\)

样例输出1:

0 28 12

样例输出2:

0 -1 -1 12


\(Source\)

练习题 树4-1-线段树


思路

首先,这道题可以很容易看出是一道最短路问题

我们首先想到就是对于\(v\)每一个\([l,r]\)区间内的数进行连边

但这样的连边的最坏复杂度都可以达到\(O(nq)\),极其容易被卡死

所以我们再仔细观察题目,不去考虑最短路的话。。。

一堆区间操作是不是很像线段树?!

于是我们使用线段树优化最短路

我们考虑对于情况2,3分别建一棵线段树,为入树和出树

其中线段树的每一个节点都表示一个区间

对于操作2,我们就可以将图的节点与入树上区间所对应的节点连边

对于操作3,我们就可以将出树上区间所对应的节点与图的节点连边

我们还需要在入树和出树他们各自自己树内建边,当然权值为\(0\)

最后跑一遍最短路\(dijkstra\)就可以了


代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=700050,M=3500010l;
const ll INF=0x7f7f7f7f7f7f7f7f;
int n,t,s,cnt=0;
int head[N];
int lc1[N],lc2[N],rc1[N],rc2[N];
//lc1[now],rc1[now]分别表示在入树内now的左右儿子,lc2,rc2是表示在出树内
int to[M<<1],nxt[M<<1];
int val[M<<1];
int nodetot=0;//最短路内的节点数
ll dis[N];
bool vis[N];
struct edge
{
    int u;
    ll cost;
    bool operator < (const edge & e)const
    {
        return cost>e.cost;
    }
}now;
priority_queue<edge>q;
void add(int u,int v,int w)
{
    to[++cnt]=v;
    nxt[cnt]=head[u];
    val[cnt]=w;
    head[u]=cnt;
}
int build1(int l,int r)
{
    if(l==r)return l;
    int now=++nodetot;
    int mid=(l+r)>>1;
    lc1[now-n]=build1(l,mid);
    add(now,lc1[now-n],0);//父亲与儿子连入边,父亲->儿子
    rc1[now-n]=build1(mid+1,r);
    add(now,rc1[now-n],0);
    return now;
}
int build2(int l,int r)
{
    if(l==r)return l;
    int now=++nodetot;
    int mid=(l+r)>>1;
    lc2[now-n]=build2(l,mid);
    add(lc2[now-n],now,0);//父亲与儿子连出边,儿子->父亲
    rc2[now-n]=build2(mid+1,r);
    add(rc2[now-n],now,0);
    return now;
}
void modify1(int v,int l,int r,int w,int ql,int qr,int k)
{
    if(l<=ql&&qr<=r)
    {
        add(v,k,w);//到达区间内,将图内的点与这个区间对应的节点连边
        return ;
    }
    int mid=(ql+qr)>>1;
    if(r<=mid)modify1(v,l,r,w,ql,mid,lc1[k-n]);
    else if(l>mid)modify1(v,l,r,w,mid+1,qr,rc1[k-n]);
    else 
    {
        modify1(v,l,mid,w,ql,mid,lc1[k-n]);
        modify1(v,mid+1,r,w,mid+1,qr,rc1[k-n]);
    }
}
void modify2(int v,int l,int r,int w,int ql,int qr,int k)
{
    if(l<=ql&&qr<=r)
    {
        add(k,v,w);
        return ;
    }
    int mid=(ql+qr)>>1;
    if(r<=mid)modify2(v,l,r,w,ql,mid,lc2[k-n]);
    else if(l>mid)modify2(v,l,r,w,mid+1,qr,rc2[k-n]);
    else 
    {
        modify2(v,l,mid,w,ql,mid,lc2[k-n]);
        modify2(v,mid+1,r,w,mid+1,qr,rc2[k-n]);
    }
}
void dijkstra(int st)
{
    dis[st]=0;
    q.push((edge){st,0});
    while(!q.empty())
    {
        now=q.top();
        q.pop();
        if(vis[now.u])continue;
        vis[now.u]=1;
        dis[now.u]=now.cost;
        for(int i=head[now.u];i;i=nxt[i])
        {
            if(!vis[to[i]]&&(now.cost+(ll)val[i])<dis[to[i]])
            {
                q.push((edge){to[i],now.cost+(ll)val[i]});
            }
        }
    }
}
int main()
{
    scanf("%d %d %d",&n,&t,&s);
    memset(dis,0x7f,sizeof(dis));
    nodetot=n;
    int rt1=build1(1,n);
    int rt2=build2(1,n);
    int op,v,u,l,r,w;
    for(int i=1;i<=t;i++)
    {
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d %d %d",&v,&u,&w);
            add(v,u,w);//直接连边
        }
        if(op==2)
        {
            scanf("%d %d %d %d",&v,&l,&r,&w);
            modify1(v,l,r,w,1,n,rt1);
        }
        if(op==3)
        {
            scanf("%d %d %d %d",&v,&l,&r,&w);
            modify2(v,l,r,w,1,n,rt2);
        }
    }
    dijkstra(s);
    for(int i=1;i<=n;i++)
    {
        if(dis[i]==INF)printf("-1 ");
        else printf("%lld ",dis[i]);
    }
    return 0;
}
上一篇:【zbar源码分析】确定探测图形中心点数量


下一篇:PHP系列 | PHP5.6 安装 endroid/qr-code 遇到的问题