虚点连边求解洛谷P3393逃离僵尸岛

题目给出了若干个定义为被僵尸控制的城市,意思就是不能进入;若干个安全的城市花费为 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 bfsdfs呢?
  • 先来看如何 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值