题目给出了若干个定义为被僵尸控制的城市,意思就是不能进入;若干个安全的城市花费为 p p p,若干个危险的城市花费为 q q q,现在问从 1 1 1到 n n n的最少花费
- 思路很清晰,我们显然需要求出所有的危险城市的标号,这样跑一遍 d i j k s t r a dijkstra dijkstra就行了,问题是如何高效地筛选出危险城市呢?
- 和僵尸控制的城市之间距离小于 s s s的城市被称为危险城市,因为僵尸控制的城市是给定的,如果想从每个僵尸控制的城市出发跑若干次最短路,无疑是正确的,但是时间上显然有些紧张,因为数据范围是 1 0 5 10^5 105,那么是否可以考虑 b f s 或 者 d f s bfs或者dfs bfs或者dfs呢?
- 先来看如何 d f s dfs dfs搜索,使用 d a n g e r danger danger数组表示危险城市,程序如下
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
using namespace std;
#define int long long
const int MAXN = 2e6 + 100;
int C[MAXN];
int vis[MAXN];
int dis[MAXN];
int head[MAXN];
int danger[MAXN];
struct Edge{
int next;
int to;
int val;
}edge[MAXN];
struct st{
int ID;
int d;
bool operator<(st x)const{
return x.d < d;
}
};
int cnt;
void Add_Edge(int u, int v, int w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].val = w;
head[u] = cnt++;
}
priority_queue<st> q;
void dijstra(int s){
st now;
now.d = dis[s] = 0;
now.ID = s;
q.push(now);
while(!q.empty()){
now = q.top();
q.pop();
s = now.ID;
if(vis[s]) continue;
vis[s] = 1;
for(int i=head[s];i!=-1;i=edge[i].next){
if(!vis[edge[i].to] && dis[edge[i].to] > edge[i].val + dis[s]){
dis[edge[i].to] = dis[s] + edge[i].val;
now.ID = edge[i].to;
now.d = dis[edge[i].to];
q.push(now);
}
}
}
}
int A[MAXN], B[MAXN];
void dfs(int u, int num, int s){
if(num == s) return;
if(vis[u]) return;
vis[u] = 1;
for(int i=head[u];i!=-1;i=edge[i].next){
danger[edge[i].to] = 1;
vis[edge[i].to] = 0;
dfs(edge[i].to, num + 1, s);
}
}
signed main(){
int n, m, k, s, a, b;
int p, q;
scanf("%lld%lld%lld%lld", &n, &m, &k, &s);
scanf("%lld%lld", &p, &q);
for(int i=0;i<k;i++){
scanf("%lld", &C[i]);
}
memset(head, -1, sizeof head);
for(int i=0;i<m;i++){
scanf("%lld%lld", &a, &b);
Add_Edge(a, b, 1);
Add_Edge(b, a, 1);
A[i] = a;
B[i] = b;
}
for(int i=0;i<k;i++){
dfs(C[i], 0, s);
memset(vis, 0, sizeof vis);//!!!;
}
// for(int i=1;i<=n;i++){
// if(danger[i]) cout << i << endl;
// }
// return 0;
int w;
cnt = 0;
memset(head, -1, sizeof head);
memset(dis, 0x3f, sizeof dis);
for(int i=0;i<k;i++){
vis[C[i]] = 1;
}
for(int i=0;i<m;i++){
// if(vis[A[i]] || vis[B[i]]) continue;
if(danger[B[i]]) w = q;
else w = p;
Add_Edge(A[i], B[i], w);
if(danger[A[i]]) w = q;
else w = p;
Add_Edge(B[i], A[i], w);
}
dijstra(1);
if(!danger[n]) cout << dis[n] - p;
else cout << dis[n] - q;
return 0;
}
- 搜索时可能出现一个错误,这个问题有距离的限制,有可能出现覆盖问题,如下图,如果先搜索到4,假设搜到3刚好停止,如果将3标记,那么就有可能导致5无法被搜索到
- 解决的办法就是在将要进行 d f s dfs dfs的节点的 v i s vis vis置为0,而后在 d f s dfs dfs的时候将 v i s vis vis置为1,这样就可以防止节点之间的覆盖问题
- 但是如果使用上述 d f s dfs dfs方法,过不了最后两个点,转为使用 b f s bfs bfs,时间及格
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
using namespace std;
#define int long long
const int MAXN = 2e6 + 100;
int C[MAXN];
int vis[MAXN];
int dis[MAXN];
int head[MAXN];
int danger[MAXN];
struct Edge{
int next;
int to;
int val;
}edge[MAXN];
struct st{
int ID;
int d;
bool operator<(st x)const{
return x.d < d;
}
st(){}
st(int ID, int d){
this->ID = ID;
this->d = d;
}
};
int cnt;
void Add_Edge(int u, int v, int w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].val = w;
head[u] = cnt++;
}
priority_queue<st> q;
void dijkstra(int s){
st now;
now.d = dis[s] = 0;
now.ID = s;
q.push(now);
while(!q.empty()){
now = q.top();
q.pop();
s = now.ID;
if(vis[s]) continue;
vis[s] = 1;
for(int i=head[s];i!=-1;i=edge[i].next){
if(!vis[edge[i].to] && dis[edge[i].to] > edge[i].val + dis[s]){
dis[edge[i].to] = dis[s] + edge[i].val;
now.ID = edge[i].to;
now.d = dis[edge[i].to];
q.push(now);
}
}
}
}
int A[MAXN], B[MAXN];
void bfs(int k, int s){
queue<st> q;
st now;
while(!q.empty()) q.pop();
for(int i=0;i<k;i++){
now.ID = C[i];
now.d = 0;
dis[now.ID] = 0;
q.push(now);
while(!q.empty()){
now = q.front();
q.pop();
danger[now.ID] = 1;
if(now.d == s) continue;
vis[now.d] = 0;
for(int i=head[now.ID];i!=-1;i=edge[i].next){
int v = edge[i].to;
if(dis[v] > dis[now.ID] + 1){
dis[v] = dis[now.ID] + 1;
if(!vis[v]){
q.push(st(v, dis[v]));
vis[v] = 1;
}
}
}
}
memset(vis, 0, sizeof vis);
}
}
signed main(){
int n, m, k, s, a, b;
int p, q;
scanf("%lld%lld%lld%lld", &n, &m, &k, &s);
scanf("%lld%lld", &p, &q);
for(int i=0;i<k;i++){
scanf("%lld", &C[i]);
}
memset(head, -1, sizeof head);
for(int i=0;i<m;i++){
scanf("%lld%lld", &a, &b);
Add_Edge(a, b, 1);
Add_Edge(b, a, 1);
A[i] = a;
B[i] = b;
}
memset(dis, 0x3f, sizeof dis);
bfs(k, s);
// for(int i=1;i<=n;i++){
// if(danger[i]) cout << i << endl;
// }
// return 0;
int w;
cnt = 0;
memset(head, -1, sizeof head);
memset(dis, 0x3f, sizeof dis);
for(int i=0;i<k;i++){
vis[C[i]] = 1;
}
for(int i=0;i<m;i++){
if(vis[A[i]] || vis[B[i]]) continue;
if(danger[B[i]]) w = q;
else w = p;
Add_Edge(A[i], B[i], w);
if(danger[A[i]]) w = q;
else w = p;
Add_Edge(B[i], A[i], w);
}
dijkstra(1);
if(!danger[n]) cout << dis[n] - p;
else cout << dis[n] - q;
return 0;
}
虚点连边
- 这个思路非常优秀,想法是这样的,加上一个虚点,因为这里面 0 0 0这个点没有被用到,所以人工加上一个 0 0 0点,把这个点和被僵尸占领的城市都连上,那么如果我从这个0点开始跑一边最短路,那么如果得到 d i s dis dis数组的值 ≤ s + 1 \leq s+1 ≤s+1,那么这个城市就是危险城市,因为被僵尸占领的城市和 0 0 0点之间的距离是1,这样时间复杂度显然可以大大降低,也是这道题的正解
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
using namespace std;
#define int long long
const int MAXN = 2e6 + 100;
int C[MAXN];
int vis[MAXN];
int dis[MAXN];
int head[MAXN];
int danger[MAXN];
struct Edge{
int next;
int to;
int val;
}edge[MAXN];
struct st{
int ID;
int d;
bool operator<(st x)const{
return x.d < d;
}
st(){}
st(int ID, int d){
this->ID = ID;
this->d = d;
}
};
int cnt;
void Add_Edge(int u, int v, int w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].val = w;
head[u] = cnt++;
}
priority_queue<st> q;
void dijkstra(int s){
st now;
now.d = dis[s] = 0;
now.ID = s;
q.push(now);
while(!q.empty()){
now = q.top();
q.pop();
s = now.ID;
if(vis[s]) continue;
vis[s] = 1;
for(int i=head[s];i!=-1;i=edge[i].next){
if(!vis[edge[i].to] && dis[edge[i].to] > edge[i].val + dis[s]){
dis[edge[i].to] = dis[s] + edge[i].val;
now.ID = edge[i].to;
now.d = dis[edge[i].to];
q.push(now);
}
}
}
}
int A[MAXN], B[MAXN];
signed main(){
int n, m, k, s, a, b;
int p, q;
scanf("%lld%lld%lld%lld", &n, &m, &k, &s);
scanf("%lld%lld", &p, &q);
memset(head, -1, sizeof head);
for(int i=0;i<k;i++){
scanf("%lld", &C[i]);
Add_Edge(0, C[i], 1);
Add_Edge(C[i], 0, 1);
}
for(int i=0;i<m;i++){
scanf("%lld%lld", &a, &b);
Add_Edge(a, b, 1);
Add_Edge(b, a, 1);
A[i] = a;
B[i] = b;
}
memset(dis, 0x3f, sizeof dis);
dijkstra(0);//从0开始跑最短路,找危险城市
for(int i=1;i<=n;i++){
if(dis[i] <= s + 1) danger[i] = 1;
}
int w;
cnt = 0;
memset(head, -1, sizeof head);
memset(dis, 0x3f, sizeof dis);
memset(vis, 0, sizeof vis);
for(int i=0;i<k;i++){
vis[C[i]] = 1;
}
for(int i=0;i<m;i++){
if(vis[A[i]] || vis[B[i]]) continue;
if(danger[B[i]]) w = q;
else w = p;
Add_Edge(A[i], B[i], w);
if(danger[A[i]]) w = q;
else w = p;
Add_Edge(B[i], A[i], w);
}
dijkstra(1);
if(!danger[n]) cout << dis[n] - p;
else cout << dis[n] - q;
return 0;
}