【888题竞赛篇】第七题,2023ICPC沈阳-羊吃狼(Sheep Eat Wolves)

更多精彩内容

这里是带你游历编程世界的Dashcoding编程社,我是Dash/北航硕士/ICPC区域赛全国排名30+/给你呈现我们眼中的世界!

256题算法特训课,帮你斩获大厂60W年薪offer

原题

2023ICPC沈阳真题羊吃狼

B站动画详解

问题分析

这个问题涉及到通过BFS(广度优先搜索)和动态规划(DP)的方法来计算将所有羊安全运送回家的最少运输次数。我们需要处理的核心问题是,在任何时刻,狼的数量是否会对羊构成威胁。具体来说,当狼的数量严格大于羊的数量加上给定的阈值 q q q 时,狼会吃掉羊。因此,在设计方案时,我们必须确保在任何时刻,农夫约翰都能够控制住狼和羊,以防止狼吃掉羊。

思路分析

算法的核心思想是使用状态表示和广度优先搜索来求解最少运输次数。我们定义状态 f[i][j][side],其中 i 表示在当前岸边的羊的数量,j 表示在当前岸边的狼的数量,side 表示当前岸边是否是农夫约翰所在的岸边(0表示农夫在家那边,1表示农夫在河对岸)。使用BFS可以逐层探索所有可能的状态转移,从而找到最优解。

具体步骤如下:

  1. 初始化:将所有状态 f[i][j][side] 初始化为无穷大(INF),表示初始状态不可达。设置初始状态为 f[x][y][0] = 0,表示在初始时,所有羊和狼在家那边,且农夫也在家那边。
  2. 状态转移:从当前状态出发,尝试将不同数量的羊和狼运输到另一边。对每种运输方案进行状态转移,并更新最少步数。
  3. 合法性检查:在进行状态转移时,检查是否符合安全条件,即狼的数量是否严格大于羊的数量加上阈值 q。
    最终结果:遍历所有可能的状态,找到将所有羊安全运送到对岸的最少步数。如果找不到合法的方案,则返回 -1。

算法实现

下面是使用广度优先搜索(BFS)结合动态规划(DP)解决该问题的实现。我们将定义状态 f[i][j][side] 表示在当前岸边有 i 只羊和 j 只狼,且农夫在与家不同侧的岸边时所需的最少步数(side 为0表示农夫在家那边,1表示农夫在对岸)。

代码详解

标准代码程序

C++代码

#include <bits/stdc++.h>
using namespace std;
const int N = 105;
const int INF = 0x3f3f3f3f;

int f[N][N][3], ans = INF;
struct node {
    int x, y, side;
};

queue<node> Q;

int main() {
    memset(f, INF, sizeof(f));
    int x, y, p, q;
    cin >> x >> y >> p >> q;
    
    // 初始化起始状态
    f[x][y][0] = 0;
    Q.push(node{x, y, 0});
    
    while (!Q.empty()) {
        node state = Q.front();
        Q.pop();
        int sheap = state.x;
        int wolf = state.y;
        int otherSheap = x - sheap;
        int otherWolf = y - wolf;
        int side = state.side;
        
        // 尝试将不同数量的羊和狼运输到另一边
        for (int i = 0; i <= min(sheap, p); i++) {
            for (int j = 0; j + i <= p && j <= wolf; j++) {
                int staySheap = sheap - i;
                int stayWolf = wolf - j;
                
                // 检查安全条件
                if (stayWolf > staySheap + q && staySheap) continue;
                
                // 更新状态
                if (f[otherSheap + i][otherWolf + j][side ^ 1] > f[sheap][wolf][side] + 1) {
                    f[otherSheap + i][otherWolf + j][side ^ 1] = f[sheap][wolf][side] + 1;
                    Q.push(node{otherSheap + i, otherWolf + j, side ^ 1});
                }
            }
        }
    }
    
    // 找到最少步数
    for (int i = 0; i <= y; i++) ans = min(ans, f[x][i][1]);
    if (ans == INF) cout << -1;
    else cout << ans;
    
    return 0;
}

Java代码

import java.util.*;

public class Main {
    static final int N = 105;
    static final int INF = Integer.MAX_VALUE;
    static int[][][] f = new int[N][N][3];
    static int ans = INF;
    
    static class Node {
        int x, y, side;
        Node(int x, int y, int side) {
            this.x = x;
            this.y = y;
            this.side = side;
        }
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        for (int[][] arr2D : f) {
            for (int[] arr1D : arr2D) {
                Arrays.fill(arr1D, INF);
            }
        }
        
        int x = sc.nextInt();
        int y = sc.nextInt();
        int p = sc.nextInt();
        int q = sc.nextInt();
        
        f[x][y][0] = 0;
        Queue<Node> Q = new LinkedList<>();
        Q.add(new Node(x, y, 0));
        
        while (!Q.isEmpty()) {
            Node state = Q.poll();
            int sheap = state.x;
            int wolf = state.y;
            int otherSheap = x - sheap;
            int otherWolf = y - wolf;
            int side = state.side;
            
            for (int i = 0; i <= Math.min(sheap, p); i++) {
                for (int j = 0; j + i <= p && j <= wolf; j++) {
                    int staySheap = sheap - i;
                    int stayWolf = wolf - j;
                    
                    if (stayWolf > staySheap + q && staySheap > 0) continue;
                    
                    if (f[otherSheap + i][otherWolf + j][side ^ 1] > f[sheap][wolf][side] + 1) {
                        f[otherSheap + i][otherWolf + j][side ^ 1] = f[sheap][wolf][side] + 1;
                        Q.add(new Node(otherSheap + i, otherWolf + j, side ^ 1));
                    }
                }
            }
        }
        
        for (int i = 0; i <= y; i++) {
            ans = Math.min(ans, f[x][i][1]);
        }
        System.out.println(ans == INF ? -1 : ans);
    }
}

Python代码

from collections import deque

def main():
    INF = float('inf')
    N = 105
    f = [[[INF] * 3 for _ in range(N)] for _ in range(N)]
    ans = INF
    
    x, y, p, q = map(int, input().split())
    
    f[x][y][0] = 0
    Q = deque([(x, y, 0)])
    
    while Q:
        sheap, wolf, side = Q.popleft()
        otherSheap = x - sheap
        otherWolf = y - wolf
        
        for i in range(min(sheap, p) + 1):
            for j in range(min(p - i, wolf) + 1):
                staySheap = sheap - i
                stayWolf = wolf - j
                
                if stayWolf > staySheap + q and staySheap > 0:
                    continue
                
                if f[otherSheap + i][otherWolf + j][side ^ 1] > f[sheap][wolf][side] + 1:
                    f[otherSheap + i][otherWolf + j][side ^ 1] = f[sheap][wolf][side] + 1
                    Q.append((otherSheap + i, otherWolf + j, side ^ 1))
    
    for i in range(y + 1):
        ans = min(ans, f[x][i][1])
    
    print(-1 if ans == INF else ans)

if __name__ == "__main__":
    main()

Javascript代码

function minTransport(x, y, p, q) {
    const N = 105;
    const INF = Infinity;
    let f = Array.from({ length: N }, () => 
        Array.from({ length: N }, () => [INF, INF, INF])
    );
    let ans = INF;
    
    // 初始状态
    f[x][y][0] = 0;
    let queue = [{ x, y, side: 0 }];
    
    while (queue.length > 0) {
        let { x: sheap, y: wolf, side } = queue.shift();
        let otherSheap = x - sheap;
        let otherWolf = y - wolf;
        
        for (let i = 0; i <= Math.min(sheap, p); i++) {
            for (let j = 0; j + i <= p && j <= wolf; j++) {
                let staySheap = sheap - i;
                let stayWolf = wolf - j;
                
                if (stayWolf > staySheap + q && staySheap > 0) continue;
                
                if (f[otherSheap + i][otherWolf + j][side ^ 1] > f[sheap][wolf][side] + 1) {
                    f[otherSheap + i][otherWolf + j][side ^ 1] = f[sheap][wolf][side] + 1;
                    queue.push({ x: otherSheap + i, y: otherWolf + j, side: side ^ 1 });
                }
            }
        }
    }
    
    for (let i = 0; i <= y; i++) {
        ans = Math.min(ans, f[x][i][1]);
    }
    
    return ans === INF ? -1 : ans;
}

复杂度分析

时间复杂度

在这个问题中,我们使用了广度优先搜索(BFS)来解决问题。下面是时间复杂度的详细分析:

  1. 状态空间

    • 状态表示为 ( sheep , wolf , side ) (\text{sheep}, \text{wolf}, \text{side}) (sheep,wolf,side),其中 sheep \text{sheep} sheep 的取值范围是 0 0 0 x x x wolf \text{wolf} wolf 的取值范围是 0 0 0 y y y side \text{side} side 取值为 0 0 0 1 1 1
    • 因此,状态空间的总大小为 ( x + 1 ) × ( y + 1 ) × 2 (x + 1) \times (y + 1) \times 2 (x+1)×(y+1)×2,即 O ( x × y ) O(x \times y) O(x×y)
  2. 状态转移

    • 对于每个状态 ( sheep , wolf , side ) (\text{sheep}, \text{wolf}, \text{side}) (sheep,wolf,side),我们尝试将不同数量的羊和狼运输到对岸。最坏情况下,我们需要检查 p p p 个位置的羊和狼组合。
    • 因此,每个状态的处理时间为 O ( p 2 ) O(p^2) O(p2)
  3. 总体时间复杂度

    • 状态空间的大小是 O ( x × y ) O(x \times y) O(x×y)
    • 每个状态的处理时间是 O ( p 2 ) O(p^2) O(p2)
    • 因此,总体时间复杂度是 O ( x × y × p 2 ) O(x \times y \times p^2) O(x×y×p2)

空间复杂度

  1. 状态存储
  • 使用了一个三维数组 f f f 来存储状态信息,其大小为 ( x + 1 ) × ( y + 1 ) × 2 (x + 1) \times (y + 1) \times 2 (x+1)×(y+1)×2,即 O ( x × y ) O(x \times y) O(x×y)
  1. 队列存储

    • BFS 使用了一个队列来存储状态。队列中最多可能有 O ( x × y ) O(x \times y) O(x×y) 个状态,每个状态占用常量空间。
  2. 总体空间复杂度

    • 状态存储和队列存储的空间复杂度是 O ( x × y ) O(x \times y) O(x×y)

总结

这个算法通过广度优先搜索(BFS)和动态规划(DP)的结合,能够有效地解决将羊安全运输到对岸的问题。时间复杂度为 O ( x × y × p 2 ) O(x \times y \times p^2) O(x×y×p2),空间复杂度为 O ( x × y ) O(x \times y) O(x×y),使得它在给定的输入范围内( x , y ≤ 100 x, y \leq 100 x,y100)能够在合理的时间内运行。算法确保了所有可能的运输方案都被探索,并找到最优解。如果无法安全运输所有羊,则返回 − 1 -1 1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值