《算法竞赛·快冲300题》每日一题:“寻宝”

算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。


寻宝” ,链接: http://oj.ecustacm.cn/problem.php?id=1707

题目描述

【题目描述】 寻宝游戏中有红宝石和蓝宝石,每个点至多存在一种宝石。
   在n个点的单向图中,玩家每占领一个点,都需要在这个点上插上一面旗帜。
   最开始玩家占领起点1,已经在起点1插入一面旗帜,之后每次只能从已经占领的点出发,然后占领相邻的点,当占领区域中至少包含1个红宝石和1个蓝宝石时,游戏胜利。
   请问玩家最少还需要多少面旗帜可以胜利(不包括起点1的旗帜)。
【输入格式】 输入第一行包含三个正整数n,m,k(2≤n≤10^5,1≤m,k<n),分别表示点数、红宝石总数、蓝宝石总数。
   第二行包含m个数字,表示包含红宝石的点。
   第三行包含k个数字,表示包含蓝宝石的点。
   接下来n行,第i行:第一个数字k表示点i的出度,第2到k+1个数字表示点i可到达的点。
【输出格式】 如果不可能胜利,输出“impossible”,否则输出一个数字表示答案。
【输入样例】

样例13 1 1
2
3
1 2
2 3 1
1 1

样例23 1 1
2
3
1 2
1 1
2 1 2

【输出样例】

样例12

样例2:
impossible

题解

   给出一张有向图,其中一些点有红宝石,一些点有蓝宝石,也可能没有宝石。问要走到至少1个红点和一个蓝点,最少需要走多少步。
   这显然是最短路径问题,它至少是2个最短路:第一次到红点(不经过蓝点),第二次到蓝点;或者反过来先蓝点再红点。
   每个点有3种可能:红、蓝、无色。把一条能走到红和蓝的路径分解为三部分,它们通过点i中转:从1走到i(红、蓝、无色三者之一),i走到蓝(或红),再走到红(或蓝)。遍历所有的i,最小的路径就是答案。
   代码的步骤是:
   (1)计算从起点1到其他所有点的最短距离。
   (2)计算从任意点i到红点的最短距离。因为红点很多,用这样一个技巧:新增一个点0,它的邻居是所有红点。那么从点i到任意红点的最短距离,等于到0的最短距离减1。
   (3)计算从任意点i到蓝点的最短距离。同样新增一个点n+1,它的邻居是所有蓝点,从i到任意蓝点的距离,等于i到点n+1的最短距离减1。
   对点i的以上3个距离相加,是经过i的最短路径。比较所有的i的最短路径,最小值就是答案。
   编码用BFS求最短路。因为边长都是1,所以用BFS求最短路是最好的,复杂度O(n),不需要用其他最短路算法。
   (1)求点1到其他点的最短路,执行一次BFS即可。
   (2)求任意点i到点0的最短路,相当于在反图上,求起点0到其他所有点的最短路。
   (3)求任意点i到点n+1的最短路,相当于在反图上,求起点n+1到其他所有点的罪状让路。
   注意BFS用的队列要判重。一般编码时定义int vis[n]来判断是否已经用队列处理过,若vis[i]=1表示i已经用队列处理过,i不再进入队列。下面的代码没有用这种方法,而是用了一个隐含的判重。第19行当d[x]=inf时,说明x点还没有用队列处理过,需要放进队列;否则x点的最短路径已经算过,不用再放进队列。
   下面的C++代码还有一个细节,把inf定义为0x3f3f3f,它比n=105大就行,因为最长路径不会超过n。不要定义为更大的0x3f3f3f3f,因为第48行的3个d[]相加,最大等于3*inf,超过了int。
   代码的复杂度,做3次BFS,最后求最小值,总复杂度O(n)。
【重点】 反图,最短路 。

C++代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, inf = 0x3f3f3f;     //注意不要写成0x3f3f3f3f,会导致3*inf超过int
vector<int> G[N], NG[N];                    //原图,反图
int d0[N], d1[N], dn[N];
int n, m, k;
void add(int a, int b) {
    
    
    G[a].push_back(b);                      //原图加边:a->b
    NG[b].push_back(a);                     //反图加边:b->a
}
void bfs(int s, int *d, vector<int> *G) {
    
       //求s到其他所有点的最短路,d[i]是s->i的最短路
    for (int i=0; i<=n+1; i++)  d[i] = inf;
    queue<int> q;
    q.push(s);
    d[s] = 0;
    while (q.size()) {
    
    
        int t = q.front();  q.pop();
        for (auto x : G[t]) {
    
            //扩展t的邻居点
            if(d[x] == inf) {
    
            //有判重的作用。如果不等于inf,说明已经算过,不用进队列
                d[x] = d[t] + 1;
                q.push(x);
            }
        }
    }
}
int main() {
    
    
    scanf("%d%d%d",&n,&m,&k);
    for (int i = 1; i <= m; i++) {
    
      // m个红宝石
        int x; scanf("%d",&x);
        add(x, 0);                  //加边(x,0)。把所有红宝石和0点连接
    }
    for (int i = 1; i <= k; i++) {
    
      //k个蓝宝石
        int x; scanf("%d",&x);
        add(x, n + 1);              //加边(x,n+1)。把所有蓝宝石和n+1点连接
    }
    for (int i = 1; i <= n; i++) {
    
    
        int j; scanf("%d",&j);      //第i点的邻居点
        while (j--) {
    
    
            int x; scanf("%d",&x);
            add(i, x);              //加边:i-x
        }
    }
    bfs(0,   d0, NG); //在反图上计算所有点到0点(终点是红宝石)的最短路,d0[i]是i到0的最短路
    bfs(1,   d1, G);  //在原图上计算1到所有点的最短路,d1[i]是1到i的最短路
    bfs(n+1, dn, NG); //在反图上计算所有点到n+1(终点是蓝宝石)的最短路,dn[i]是i到+1的最短路
    int ans = inf;
    for (int i = 1; i <= n; i++)
        ans = min(ans, d0[i] + d1[i] + dn[i]);  //3个d相加,最大可能 = 3*inf,小心越界
    if(ans == inf)  printf("impossible\n");
    else printf("%d\n",ans - 2);
    return 0;
}

Java代码

import java.util.*;
public class Main {
    
    
    static final int N = 100010, inf = 0x3f3f3f;
    static List<Integer>[] G = new ArrayList[N];   //原图
    static List<Integer>[] NG = new ArrayList[N];  //反图
    static int[] d0 = new int[N], d1 = new int[N], dn = new int[N];
    static int n, m, k;
    public static void main(String[] args) {
    
    
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        k = sc.nextInt();
        for (int i = 0; i <= n + 1; i++) {
    
    
            G[i] = new ArrayList<>();
            NG[i] = new ArrayList<>();
        }
        for (int i = 1; i <= m; i++) {
    
      // m个红宝石
            int x = sc.nextInt();
            add(x, 0);
        }
        for (int i = 1; i <= k; i++) {
    
      //k个蓝宝石
            int x = sc.nextInt();
            add(x, n + 1);
        }
        for (int i = 1; i <= n; i++) {
    
    
            int j = sc.nextInt();
            while (j-- > 0) {
    
    
                int x = sc.nextInt();
                add(i, x);
            }
        }
        bfs(0, d0, NG);
        bfs(1, d1, G);
        bfs(n + 1, dn, NG);
        int ans = inf;
        for (int i = 1; i <= n; i++)
            ans = Math.min(ans, d0[i] + d1[i] + dn[i]);
        if (ans == inf) System.out.println("impossible");
        else            System.out.println(ans - 2);
    }
    static void add(int a, int b) {
    
    
        G[a].add(b);    //原图加边:a->b
        NG[b].add(a);   //反图加边:b->a
    }
    static void bfs(int s, int[] d, List<Integer>[] G) {
    
      //求s到其他所有点的最短路
        Arrays.fill(d, inf);
        Queue<Integer> q = new LinkedList<>();
        q.offer(s);
        d[s] = 0;
        while (!q.isEmpty()) {
    
    
            int t = q.poll();
            for (int x : G[t]) {
    
        //扩展t的邻居点
                if (d[x] == inf) {
    
      //有判重的作用。如果不等于inf,说明已经算过,不用进队列
                    d[x] = d[t] + 1;
                    q.offer(x);
                }
            }
        }
    }
}

Python代码

from collections import deque
N = 100010
inf = 0x3f3f3f
G = [[] for _ in range(N)]  # 原图
NG = [[] for _ in range(N)]  # 反图
d0 = [inf] * N
d1 = [inf] * N
dn = [inf] * N
n, m, k = 0, 0, 0
def add(a, b):
    G[a].append(b)  # 原图加边:a->b
    NG[b].append(a)  # 反图加边:b->a
def bfs(s, d, G):  # 求s到其他所有点的最短路,d[i]是s->i的最短路
    global inf
    d[s] = 0
    q = deque()
    q.append(s)
    while q:
        t = q.popleft()
        for x in G[t]:  # 扩展t的邻居点
            if d[x] == inf:  # 有判重的作用。如果不等于inf,说明已经算过,不用进队列
                d[x] = d[t] + 1
                q.append(x)
if __name__ == '__main__':
    n, m, k = map(int, input().split())
    a = [0] + list(map(int, input().split()))
    for i in range(1, m + 1):  # m个红宝石
        x = a[i]
        add(x, 0)
    b = [0] + list(map(int, input().split()))
    for i in range(1, k + 1):  # k个蓝宝石
        x = b[i]
        add(x, n + 1)
    for i in range(1, n + 1):
        nums = list(map(int, input().split()))
        for j in range(1, len(nums)):
            x = nums[j]
            add(i, x)
    bfs(0, d0, NG)
    bfs(1, d1, G)
    bfs(n + 1, dn, NG)
    ans = inf
    for i in range(1, n + 1):  ans = min(ans, d0[i] + d1[i] + dn[i])
    if ans == inf:    print("impossible")
    else:             print(ans - 2)

猜你喜欢

转载自blog.csdn.net/weixin_43914593/article/details/132391048