《算法竞赛·快冲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”,否则输出一个数字表示答案。
【输入样例】
样例1:
3 1 1
2
3
1 2
2 3 1
1 1
样例2:
3 1 1
2
3
1 2
1 1
2 1 2
【输出样例】
样例1:
2
样例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)