1 solutions

  • 0
    @ 2024-9-3 18:25:05

    题目描述

    这道题目要求在一个 m × n 的矩阵中,选择最多 k 个派件点,使得派件点能够覆盖尽可能多的楼栋 (B)。派件点的覆盖范围是在同一行或同一列内,距离不超过 s 且不被防御设施 (#) 阻挡的楼栋。快递员只能移动到空地 (0) 或楼栋 (B) 上,不能穿过防御设施

    思路

    思路步骤 1.确定可达点:

    使用广度优先搜索(BFS)从快递员的起始位置 [row, col] 出发,找到所有可以到达的点(0 或 B),这些点是潜在的派件点。

    2.标记楼栋位置:

    遍历整个矩阵,记录所有楼栋 (B) 的位置,并为每个楼栋分配一个唯一的编号,用于后续的位运算。

    3.计算每个派件点的覆盖:

    对于每个可达的派件点,计算其在同一行和同一列内,不超过 s 的距离且不被 # 阻挡的所有楼栋。 使用位运算将这些楼栋的编号表示为一个位掩码(Bitmask)。

    4.选择派件点:

    使用回溯(DFS)或动态规划的方法,选择最多 k 个派件点,使得它们的覆盖位掩码的并集最大。 由于 k 较小,可以通过组合枚举或贪心策略进行优化。 5.剪枝优化:

    在搜索过程中,如果当前选择的派件点数量已达到 k,或无法超过当前已记录的最大覆盖数,立即停止该分支的搜索。

    6.输出结果:

    最终输出最大的覆盖楼栋数量。

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 35;
    int n, m;
    char a[N][N];
    int k, s;
    int ans;
    bool ch(int x, int y) {		//检查(x,y)是否在图内并不是墙
    	return min(x, y) >= 0 && x < n && y < m && a[x][y] != '#';
    }
    int dx[] = { 1,0,-1,0 };	//四个方向横坐标偏移量
    int dy[] = { 0,1,0,-1 };	//纵坐标偏移量
    bool bk[N][N];			//是否搜索过
    vector<pair<int, int>> v;	//所有合法点集合
    void dfs(int x, int y) {
    	if (bk[x][y]) return;	//如果当前点搜索过直接return
    	bk[x][y] = 1;		//标记为搜索过
    	v.push_back({ x, y });	//放入点集
    	for (int i = 0; i < 4; i++) {	//遍历四个方向
    		int tx = x + dx[i], ty = y + dy[i];
    		if (!ch(tx, ty)) continue;	//如果出地图或者是墙,继续遍历下一个方向
    		dfs(tx, ty);	//dfs搜索
    	}
    }
    void dfs2(int now, int cnt, int nows) {		//代表选到v中的第now个点,还剩cnt个派件点可以选,当前答案为nows
    	if (cnt == 0 || now == v.size()) {	//如果选完了cnt个派件点或者遍历完所有的点
    		ans = max(ans, nows);		//更新答案并返回
    		return;
    	}
    	//剪枝,如果剩余的所有点都选满(4*s+1)个楼房还不比当前答案大,则直接返回
    	if(nows + cnt*(4*s+1) <= ans)return;
    	vector<pair<int, int>> fix;		//记录当前状态中被修改的点,用于dfs状态回溯
    	for (int i = 0; i < 4; i++) {		//遍历四个方向各s格
    		for (int j = 0; j <= s; j++) {
    			int tx = v[now].first + dx[i] * j;
    			int ty = v[now].second + dy[i] * j;
    			if (!ch(tx, ty))break;		//如果出地图或者是墙,遍历下一个方向,break当前循环
    			if (a[tx][ty] == 'B') {		//如果是楼,将当前楼置为空地防止重复计算
    				fix.push_back({ tx, ty });	//放入fix中之后回溯时还原
    				a[tx][ty] = '0';		//置为空地
    			}
    		}
    	}
    	dfs2(now + 1, cnt - 1, nows+fix.size());	//遍历下一个点,选了当前点,当前答案增加fix.size()
    	for(auto g:fix) {		//还原
    		a[g.first][g.second] = 'B';
    	}
    	dfs2(now + 1, cnt, nows);		//遍历下一个点,不选当前点,当前答案不变
    }
    int main()
    {
    	int x, y;
    	cin >> n >> m >> x >> y >> k >> s;	//输入
    	for (int i = 0; i < n; i++) {
    		for (int j = 0; j < m; j++) {
    			cin >> a[i][j];
    		}
    	}
    	dfs(x, y);		//搜索出所有的从起始点能到达的点。
    	random_shuffle(v.begin(), v.end());		//随机排列一下,降低期望复杂度
    	dfs2(0, k, 0);	//选出所有状态,更新答案
    	cout << ans << endl;	//输出答案
    }
    

    python

    import sys
    import random
    
    # 提高递归深度限制,防止递归过深导致的错误
    sys.setrecursionlimit(1000000)
    
    # 定义四个方向的移动(下、右、上、左)
    dx = [1, 0, -1, 0]  # 下、右、上、左
    dy = [0, 1, 0, -1]
    
    # 全局变量初始化
    n, m = 0, 0        # 地图的行数和列数
    a = []             # 地图矩阵
    k, s = 0, 0        # 派件点数量和覆盖范围
    ans = 0            # 最大覆盖的楼栋数量
    bk = []            # 是否搜索过的标记矩阵
    v = []             # 所有可达的点集合
    
    def ch(x, y):
        """
        检查坐标 (x, y) 是否在地图内且不是墙
        """
        return 0 <= x < n and 0 <= y < m and a[x][y] != '#'
    
    def dfs(x, y):
        """
        深度优先搜索,收集所有从起始点可达的点
        :param x: 当前点的行坐标
        :param y: 当前点的列坐标
        """
        global ans
        if bk[x][y]:
            return  # 如果当前点已经被搜索过,直接返回
        bk[x][y] = True  # 标记当前点为已搜索
        v.append((x, y))  # 将当前点加入可达点集合
        for i in range(4):  # 遍历四个方向
            tx = x + dx[i]
            ty = y + dy[i]
            if ch(tx, ty):
                dfs(tx, ty)  # 递归搜索新的坐标
    
    def dfs2(now, cnt, nows):
        """
        递归选择派件点的函数,尝试覆盖更多的楼栋
        :param now: 当前正在考虑的点在可达点集合中的索引
        :param cnt: 剩余可以选择的派件点数量
        :param nows: 当前已经覆盖的楼栋数量
        """
        global ans
        if cnt == 0 or now == len(v):
            # 如果没有剩余派件点可以选择,或者已经遍历完所有点
            ans = max(ans, nows)  # 更新答案
            return
        # 剪枝:如果即使选择剩余的所有派件点,每个派件点最多覆盖 (4*s +1) 个楼栋
        # 仍然无法超过当前的答案,则提前返回
        if nows + cnt * (4 * s + 1) <= ans:
            return
        fix = []  # 记录当前状态中被修改的楼栋,用于回溯
        x, y = v[now]  # 当前考虑的派件点坐标
        for i in range(4):  # 遍历四个方向
            for j in range(0, s + 1):  # 修正为从 j=0 开始,包含派件点自身
                tx = x + dx[i] * j
                ty = y + dy[i] * j
                if not ch(tx, ty):
                    break  # 如果出地图或者遇到墙,停止该方向的遍历
                if a[tx][ty] == 'B':
                    fix.append((tx, ty))  # 记录被覆盖的楼栋
                    a[tx][ty] = '0'        # 将楼栋标记为已覆盖,防止重复计算
        # 选择当前派件点
        dfs2(now + 1, cnt - 1, nows + len(fix))
        # 回溯,恢复被覆盖的楼栋
        for g in fix:
            a[g[0]][g[1]] = 'B'
        # 不选择当前派件点
        dfs2(now + 1, cnt, nows)
    
    def main():
        global n, m, k, s, ans, a, bk, v
        # 读取所有输入数据并分割为列表
        input_data = sys.stdin.read().split()
        idx = 0
        # 读取小区的行数和列数
        n = int(input_data[idx])
        m = int(input_data[idx + 1])
        idx += 2
        # 读取快递员的初始位置
        x = int(input_data[idx])
        y = int(input_data[idx + 1])
        idx += 2
        # 读取最多可以选择的派件点数量
        k = int(input_data[idx])
        # 读取派件点的覆盖范围
        s = int(input_data[idx + 1])
        idx += 2
        # 读取地图布局
        a = [['0' for _ in range(m)] for _ in range(n)]
        for i in range(n):
            if idx >= len(input_data):
                break
            line = input_data[idx]
            for j in range(m):
                if j < len(line):
                    a[i][j] = line[j]
                else:
                    a[i][j] = '0'  # 默认为空地
            idx += 1
        # 初始化标记矩阵和可达点集合
        bk = [[False for _ in range(m)] for _ in range(n)]
        v = []
        # 执行深度优先搜索,收集所有可达点
        if ch(x, y):
            dfs(x, y)
        # 随机打乱可达点顺序,降低期望复杂度
        random.shuffle(v)
        # 执行递归选择派件点,更新答案
        dfs2(0, k, 0)
        # 输出结果
        print(ans)
    
    if __name__ == "__main__":
        main()
    

    java

    import java.util.*;
    
    public class Main {
        // 定义四个方向的移动(下、右、上、左)
        static int[] dx = {1, 0, -1, 0}; // 下、右、上、左
        static int[] dy = {0, 1, 0, -1};
        
        // 地图的行数和列数
        static int m, n;
        // 地图矩阵
        static char[][] a;
        // 派件点数量和覆盖范围
        static int k, s;
        // 最大覆盖的楼栋数量
        static int ans = 0;
        // 是否搜索过的标记矩阵
        static boolean[][] bk;
        // 所有可达的点集合
        static List<Point> v = new ArrayList<>();
        
        // Point 类表示坐标点
        static class Point {
            int x, y;
            Point(int x, int y){
                this.x = x;
                this.y = y;
            }
        }
        
        /**
         * 检查坐标 (x, y) 是否在地图内且不是墙
         * @param x 行坐标
         * @param y 列坐标
         * @return 如果合法且不是墙,返回 true,否则返回 false
         */
        static boolean ch(int x, int y){
            return (0 <= x && x < m) && (0 <= y && y < n) && (a[x][y] != '#');
        }
        
        /**
         * 深度优先搜索,收集所有从起始点可达的点
         * @param x 当前点的行坐标
         * @param y 当前点的列坐标
         */
        static void dfs(int x, int y){
            if(bk[x][y]) return; // 如果当前点已经被搜索过,直接返回
            bk[x][y] = true; // 标记当前点为已搜索
            v.add(new Point(x, y)); // 将当前点加入可达点集合
            for(int i = 0; i < 4; i++){ // 遍历四个方向
                int tx = x + dx[i];
                int ty = y + dy[i];
                if(ch(tx, ty)){
                    dfs(tx, ty); // 递归搜索新的坐标
                }
            }
        }
        
        /**
         * 递归选择派件点的函数,尝试覆盖更多的楼栋
         * @param now 当前正在考虑的点在可达点集合中的索引
         * @param cnt 剩余可以选择的派件点数量
         * @param nows 当前已经覆盖的楼栋数量
         */
        static void dfs2(int now, int cnt, int nows){
            if(cnt == 0 || now == v.size()){
                // 如果没有剩余派件点可以选择,或者已经遍历完所有点
                ans = Math.max(ans, nows); // 更新答案
                return;
            }
            // 剪枝:如果即使选择剩余的所有派件点,每个派件点最多覆盖 (4*s +1) 个楼栋
            // 仍然无法超过当前的答案,则提前返回
            if(nows + cnt * (4 * s + 1) <= ans){
                return;
            }
            // 当前考虑的派件点坐标
            Point current = v.get(now);
            int x = current.x;
            int y = current.y;
            // 记录当前状态中被修改的楼栋,用于回溯
            List<Point> fix = new ArrayList<>();
            for(int i = 0; i < 4; i++){ // 遍历四个方向
                for(int j = 0; j <= s; j++){ // 修正为从 j=0 开始,包含派件点自身
                    int tx = x + dx[i] * j;
                    int ty = y + dy[i] * j;
                    if(!ch(tx, ty)){
                        break; // 如果出地图或者遇到墙,停止该方向的遍历
                    }
                    if(a[tx][ty] == 'B'){
                        fix.add(new Point(tx, ty)); // 记录被覆盖的楼栋
                        a[tx][ty] = '0'; // 将楼栋标记为已覆盖,防止重复计算
                    }
                }
            }
            // 选择当前派件点
            dfs2(now + 1, cnt - 1, nows + fix.size());
            // 回溯,恢复被覆盖的楼栋
            for(Point g : fix){
                a[g.x][g.y] = 'B';
            }
            // 不选择当前派件点
            dfs2(now + 1, cnt, nows);
        }
        
        public static void main(String[] args){
            Scanner sc = new Scanner(System.in);
            // 读取小区的行数和列数
            m = sc.nextInt();
            n = sc.nextInt();
            // 读取快递员的初始位置
            int row = sc.nextInt();
            int col = sc.nextInt();
            // 读取最多可以选择的派件点数量
            k = sc.nextInt();
            // 读取派件点的覆盖范围
            s = sc.nextInt();
            sc.nextLine(); // 读取换行符
            // 初始化地图矩阵
            a = new char[m][n];
            for(int i = 0; i < m; i++){
                String line = sc.nextLine();
                for(int j = 0; j < n; j++){
                    if(j < line.length()){
                        a[i][j] = line.charAt(j);
                    }
                    else{
                        a[i][j] = '0'; // 默认为空地
                    }
                }
            }
            // 初始化标记矩阵
            bk = new boolean[m][n];
            // 执行深度优先搜索,收集所有可达点
            if(ch(row, col)){
                dfs(row, col);
            }
            // 随机打乱可达点顺序,降低期望复杂度
            Collections.shuffle(v, new Random());
            // 执行递归选择派件点,更新答案
            dfs2(0, k, 0);
            // 输出结果
            System.out.println(ans);
            sc.close();
        }
    }
    
    • 1

    2022.10.12.-秋招-第三题-快递员

    Information

    ID
    12
    Time
    1000ms
    Memory
    256MiB
    Difficulty
    7
    Tags
    # Submissions
    42
    Accepted
    5
    Uploaded By