题解——逃离僵尸岛(BFS+最短路+虚拟节点)

Posted ssw02

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解——逃离僵尸岛(BFS+最短路+虚拟节点)相关的知识,希望对你有一定的参考价值。

题解——逃离僵尸岛(BFS+最短路+虚拟节点)

一道很巧妙的最短路问题,细节也要注意


题面

Description
小a住的国家被僵尸侵略了!小a打算逃离到该国唯一的国际空港逃出这个国家。

该国有N个城市,城市之间有道路相连。一共有M条双向道路。保证没有自环和重边。

K个城市已经被僵尸控制了,如果贸然闯入就会被感染TAT...所以不能进入。由其中任意城市经过不超过S条道路就可以到达的别的城市,就是危险城市。换句话说只要某个没有被占城市到某个被占城市不超过s距离,就是危险。

小a住在1号城市,国际空港在N号城市,这两座城市没有被侵略。小a走每一段道路(从一个城市直接到达另外一个城市)得花一整个白天,所以晚上要住旅店。安全的的城市旅馆比较便宜要P元,而被危险的城市,旅馆要进行安保措施,所以会变贵,为Q元。所有危险的城市的住宿价格一样,安全的城市也是。在1号城市和N城市,不需要住店。

小a比较抠门,所以他希望知道从1号城市到N号城市所需要的最小花费。

输入数据保证存在路径,可以成功逃离。输入数据保证他可以逃离成功。

Input
第一行4个整数(N,M,K,S)
第二行2个整数(P,Q)
接下来K行,ci,表示僵尸侵占的城市
接下来M行,ai,bi,表示一条无向边

Output
一个整数表示最低花费

in.1
13 21 1 1
1000 6000
7
1 2
3 7
2 4
5 8
8 9
2 5
3 4
4 7
9 10
10 11
5 9
7 12
3 6
4 5
1 3
11 12
6 7
8 11
6 13
7 8
12 13

out.1
11000

数据范围与约定
对于20%数据,N<=50
对于100%数据,2 ≦ N ≦ 100000, 1 ≦ M ≦ 200000, 0 ≦ K ≦ N - 2, 0 ≦ S ≦ 100000
1 ≦ P < Q ≦ 100000

思路

主要分为两部分:

1.处理出哪些点是危险节点,哪些点是安全节点。
2.点权下放为边权

处理:
1.显然不可能对每一个僵尸围城的点做一次BFS,显然超时,原因就是有重复点被多次遍历,如果我们仅用vis数组判重,又会截断点之间的深度deep关系(可能有更加浅的点来更新这个点)。
所以,我们建立一个虚拟源点,向所有僵尸围城的点连单向边。(由于ssw02只建一个图,所以单向边,绝对不能返回 ),这样可以较好地保持点之间的深度deep关系。

2.这道题很特殊,一条单向边的边权之和这条边的目的地有关,我们只要处理问题一,把每一条边抽出来加边权即可。

细节:
1.初始用虚拟源点时只能连单向边。
2.如果只用一个图,注意初始边权为0,后面加边权。
3.加边权时注意优先级!!
4.加边权时,碰到僵尸围城的点,把两条边都赋值为inf (严格意义下加指向其的单边也行)
5.边的数量会因为虚拟的边而增多 100000 条 单向边(这可以不乘2)

AC code: 我写的相当简洁

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
const int MAXN = 100005 , MAXM = 200005 + 100000 ;//多加的边数 
inline int read()
    int s=0 ; char g=getchar() ;while(g>'9'||g<'0')g=getchar() ;
    while(g>='0'&&g<='9')s=s*10+g-'0',g=getchar();return s ;
 
int  N , M , K , S , Q1 , Q2 , head[ MAXN ] , danger[ MAXN ] , to[ MAXM*2 ] , nex[ MAXM*2 ]  , tot = 1 ; 
ll dis[ MAXN ] , w[ MAXM*2 ] ;
bool vis[ MAXN ]  ;
priority_queue< pair<ll,int> >q ;
queue< pair<int,int> >ql ;
void  add( int  x , int  y , int  z )
    to[ ++tot ] = y , nex[ tot ] = head[ x ] ,w[ tot ] = z , head[ x ] = tot ;  

void  bfs()
    ql.push( make_pair(0,0) ) ;
    vis[ 0 ] = true ;
    while( !ql.empty() )
        int  u = ql.front().first , dep = ql.front().second ; ql.pop() ;
        for( int i = head[ u ] ; i ; i = nex[ i ] )
            if( vis[ to[ i ] ] || dep > S )continue ;
            ql.push( make_pair( to[ i ] , dep+1 ) ) ;
            vis[ to[i] ] = true , danger[ to[ i ] ] = max( danger[ to[i] ] , 1 ) ;
        
    

void  dijkstra()
    for( int i = 1 ; i <= N ; ++i )dis[ i ] = 20000000005 ;//题意所得最大 20000000000
    dis[ 1 ] = 0 ;
    q.push( make_pair( 0LL,1 ) ) ;
    while( !q.empty() )
        int  u = q.top().second ; q.pop() ;
        if( vis[ u ] )continue ;
        vis[ u ] = true ;
        for( register int i = head[ u ] ; i ; i = nex[ i ] )
            if( dis[ to[ i ] ] > dis[ u ] + w[ i ] )
                dis[ to[ i ] ] = dis[ u ] + w[ i ] ;
                q.push( make_pair( -dis[ to[ i ] ] , to[ i ] ) ) ;
            
        
    

int main()
    N = read() , M = read() , K = read() , S = read() , Q1 = read() , Q2 = read() ;
    int m1 , m2 ;
    for( int i = 1 ; i <= K ; ++i )
        m1 = read() , danger[ m1 ] = 2 , add( 0 , m1 , 0 ) ;//单向 
    
    if( K%2 )tot++ ; K = tot ;
    for( int i = 1 ; i <= M ; ++i )
        m1 = read() , m2 = read() ;
        add( m1 , m2 , 0 ) ; add( m2 , m1 , 0 ) ;
    
    bfs() ;
    memset( vis , false , sizeof(vis) ) ;
    for( int i = K ; i <= tot ; ++i )//点权下放,分配边权 
        if( w[ i ] )continue ;
        if( danger[ to[i] ] == 2 )w[ i ] = w[ i^1 ] = 20000000005 ;
        else if( danger[ to[i] ] == 1 )w[ i ] = Q2 ;
            else w[ i ] = Q1 ;
        w[ i ] = ( to[ i ]  == 1 || to[ i ] == N )? 0 : w[ i ] ; 
    
    dijkstra() ;
    cout<<dis[ N ] ;
    return 0 ;

ssw02 的得分 42 -> 28 -> 57 -> 100 真是什么情况ssw02都撞上了

如有不足,请大佬指出

以上是关于题解——逃离僵尸岛(BFS+最短路+虚拟节点)的主要内容,如果未能解决你的问题,请参考以下文章

Luogu P3393 逃离僵尸岛最短路By cellur925

[单源最短路]逃离僵尸岛

图论-最短路P3393逃离僵尸岛

洛谷⑨月月赛Round2 P3393逃离僵尸岛[最短路]

Luogu P3393 逃离僵尸岛

AC日记——逃离僵尸岛 洛谷 P3393