#P2250. 第2题-安装监控器
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 599
            Accepted: 216
            Difficulty: 4
            
          
          
          
                       所属公司 : 
                              华为
                                
            
                        
              时间 :2024年11月13日-国内
                              
                      
          
 
- 
                        算法标签>DFS          
 
第2题-安装监控器
题解
问题分析
题目要求在一个 m * n 的矩阵中,给每一排的机柜放置一个监控器(每行只能放一个),同时要满足以下限制条件:
- 每列只能放一个监控器。
 - 不能在同一条对角线上(即 45度 和 135度 的斜线)放置两个监控器。
 
该问题可以看成是一个变形的 "N 皇后" 问题,目标是计算出满足所有限制条件的不同放置方案的数量。
解题思路
我们可以使用深度优先搜索(DFS)和回溯算法来求解。具体步骤如下:
- 
定义状态变量:
column_vis:用于记录已经被监控器占用的列。left_vis:用于记录已经被占用的正对角线。对角线的索引为 ROW + COL ,也就是说ROW + COL相同的都在同一行right_vis:用于记录已经被占用的反对角线。对角线的索引为ROW - COl,也就是说ROW - COL相同的都在同一行
 - 
递归函数
dfs(row):- 该函数尝试在当前行 
row放置一个监控器。 - 如果 
row等于n(行数),则说明已经成功放置了所有监控器,返回1表示找到一种有效方案。 - 初始化 
ans为0,用于累加有效方案的数量。 
 - 该函数尝试在当前行 
 - 
循环遍历每一列:
- 遍历当前行中所有列,尝试在每个列位置放置一个监控器。
 - 检查当前列和两条对角线是否已经被占用:
- 如果 
col在column_vis,或left在left_vis,或right在right_vis,则跳过当前列,继续尝试其他列。 
 - 如果 
 - 如果当前列可用,则将其加入对应的集合(表示该列和对角线被占用),并递归调用 
dfs(row + 1),继续放置下一行的监控器。 - 回溯:撤销当前行的放置,将 
col、left和right从对应集合中移除,尝试其他放置方法。 
 - 
返回结果:
- 初始调用 
dfs(0)开始从第0行放置监控器。 - 最终返回所有可能的有效方案数。
 
 - 初始调用 
 
代码实现
python
n, m = map(int, input().split())
# 初始化集合记录列和对角线的占用情况
column_vis = set()
left_vis = set()
right_vis = set()
def dfs(row):
    # 如果当前行已经是第 n 行,说明前面的行已成功放置,找到一个有效方案
    if row == n:
        return 1
    
    ans = 0  # 统计方案数
    for col in range(m):
        # 计算当前单元格的正对角线和反对角线标识
        left = row + col
        right = row - col
        
        # 检查是否可以放置监控器
        if col in column_vis or left in left_vis or right in right_vis:
            continue
        
        # 放置监控器并标记占用
        column_vis.add(col)
        left_vis.add(left)
        right_vis.add(right)
        
        # 递归搜索下一行
        ans += dfs(row + 1)
        
        # 回溯,移除占用标记
        column_vis.remove(col)
        left_vis.remove(left)
        right_vis.remove(right)
    
    return ans
# 输出所有有效方案数
print(dfs(0))
java
import java.util.HashSet;
import java.util.Scanner;
public class Main {
    static int n, m;
    static HashSet<Integer> column_vis = new HashSet<>(); // 记录已占用的列
    static HashSet<Integer> left_vis = new HashSet<>();   // 正对角线 (row + col)
    static HashSet<Integer> right_vis = new HashSet<>();  // 反对角线 (row - col)
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        m = scanner.nextInt();
        scanner.close();
        
        System.out.println(dfs(0));
    }
    static int dfs(int row) {
        if (row == n) {
            return 1; // 已放置完所有行,找到一个合法方案
        }
        
        int ans = 0;
        for (int col = 0; col < m; col++) {
            int left = row + col;
            int right = row - col;
            
            // 检查列和对角线是否被占用
            if (column_vis.contains(col) || left_vis.contains(left) || right_vis.contains(right)) {
                continue;
            }
            
            // 放置监控器并标记该列和对角线
            column_vis.add(col);
            left_vis.add(left);
            right_vis.add(right);
            
            // 递归调用下一行
            ans += dfs(row + 1);
            
            // 回溯,移除占用标记
            column_vis.remove(col);
            left_vis.remove(left);
            right_vis.remove(right);
        }
        return ans;
    }
}
C++
#include <iostream>
#include <unordered_set>
using namespace std;
int n, m;
unordered_set<int> column_vis;  // 记录已占用的列
unordered_set<int> left_vis;    // 记录正对角线占用情况 (row + col)
unordered_set<int> right_vis;   // 记录反对角线占用情况 (row - col)
int dfs(int row) {
    if (row == n) {
        return 1;  // 已放置完所有行,找到一个合法方案
    }
    
    int ans = 0;
    for (int col = 0; col < m; ++col) {
        int left = row + col;
        int right = row - col;
        
        // 检查列和对角线是否被占用
        if (column_vis.count(col) || left_vis.count(left) || right_vis.count(right)) {
            continue;
        }
        
        // 放置监控器并标记该列和对角线
        column_vis.insert(col);
        left_vis.insert(left);
        right_vis.insert(right);
        
        // 递归调用下一行
        ans += dfs(row + 1);
        
        // 回溯,移除占用标记
        column_vis.erase(col);
        left_vis.erase(left);
        right_vis.erase(right);
    }
    return ans;
}
int main() {
    cin >> n >> m;
    cout << dfs(0) << endl;
    return 0;
}
复杂度分析
- 时间复杂度:由于使用了回溯法,时间复杂度为 O(N!),其中 
N是行数。 - 空间复杂度:需要额外的空间存储列和对角线的占用情况,空间复杂度为 O(N)。
 
题目内容
某数据中心机房内摆放了M排N列机柜,现需要在每排选择一个机柜安装监控器来监视本排机柜的用电量,由于监控器在安装位置太近的话会产生相互干扰,安装时需要满足条件:安装监控器的机柜不能在同一排或同一列,并且不能在同一斜线上(45°或135*的正斜线),请问一共有多少种监控器安装方案。
输入描述
机柜排数M和列数N,值的范围[1,15);
输出描述
监控器安装方案数量。 如果没有方案返回0。
样例1
输入
2 3
输出
2
说明
第二排监控器需安装在第一排监控器相距2列以上的机柜,共有2种安装方案。图中0代表未安装监视器的机柜,1代表安装监控器的机柜。
1.方案1

2.方案2

样例2
输入
3 3
输出
0
说明
无法同时满足监控器所在机柜不在同一排、同一列、同一斜线上。图中0代表未安装监视器的机柜,1代表安装监控器的机柜。
1.第一排监控器所在机柜和第三排监控器所在机柜在同一列。

2.第二排监控器所在机柜和第三排监控器所在机柜在同一斜线。
