题解——逃离僵尸岛(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都撞上了