题解:B3791 [信息与未来 2023] 电路布线
题解:B3791 [信息与未来 2023] 电路布线
前言
没想到有这么水的蓝题,还可以写题解。
看到有大佬写了 187 行超长代码,根本不用那么复杂,我就补一篇短一点的题解把。
思路讲解
很明显可以搜索。
暴搜 dfs 枚举每一个可以接通的点,判断行不行,行的话考虑接通或不接通。不行只能不接通。
时间复杂度为 \(2^{n\times m}\times nm\),明显 TLE。
必须剪枝。
大多数 dfs 的剪枝都是预估能不能超过答案,不能就返回,这个题目也不例外。
对于一层搜索,判断其搜索完能不能超过之前的答案,不能就退出。
判断方法也是十分简单,看一下目前已连接的点加上未访问过的所有点是否 \(\le ans\),是的话就退出。
剪枝讲完了讲 check 函数,这个函数需要判断两件事情:
- 是否联通
- 是否短路
是否联通很好判断,问题是怎么判断是否短路。
只需要一直走走走,如果发现走着走着走到了之前标记过的点,并且这个点不是上一个访问的点,就短路了。
AC Code
注释版:
#include <bits/stdc++.h>
using namespace std;
// 定义常量
const int MAXN = 17; // 网格最大尺寸
const int dx[] = {0, 1, 0, -1}; // 方向数组:右、下、左、上
const int dy[] = {1, 0, -1, 0}; // 方向数组:右、下、左、上
// 全局变量
int n, m; // 网格的行数和列数
int cur; // 初始必须布线的 '+' 数量
int cnt; // 用于检查连通性时计数
char mp[MAXN][MAXN]; // 存储原始网格
char answer[MAXN][MAXN]; // 存储最优解
int sum_x, sum_y; // 初始 '+' 的坐标(用于连通性检查的起点)
bool vis[MAXN][MAXN]; // 访问标记数组
int ans = 0; // 最优解中 '+' 的数量
// 计算从当前格子到网格末尾的剩余格子数(用于剪枝)
inline int f(int x, int y) {
return n * m - (x - 1) * m - y;
}
// 检查连通性和无回路
// (x, y): 当前格子
// (x_x, y_y): 父格子(避免重复访问)
inline bool check(int x, int y, int x_x, int y_y) {
if (vis[x][y]) {
return false; // 如果已经访问过,说明存在回路
}
vis[x][y] = true; // 标记为已访问
cnt--; // 剩余未访问的 '+' 数量减一
for (int i = 0; i < 4; i++) {
int nx = x + dx[i], ny = y + dy[i]; // 四个方向移动
if (x_x == nx && y_y == ny) {
continue; // 跳过父格子
}
// 检查邻居是否是 '+' 且未访问
if (1 <= nx && nx <= n && 1 <= ny && ny <= m && mp[nx][ny] == '+') {
if (!check(nx, ny, x, y)) {
return false; // 如果邻居检查失败,直接返回 false
}
}
}
return true; // 所有邻居检查通过
}
// 深度优先搜索
// (x, y): 当前格子坐标
// sum: 当前已布线的 '+' 数量
inline void dfs(int x, int y, int sum) {
// 换行处理
if (y > m) {
x++;
y = 1;
}
// 剪枝:如果当前 sum 加上剩余格子数不超过已知最优解,直接返回
if (f(x, y) + sum <= ans) {
return;
}
// 终止条件:遍历完所有格子
if (x > n) {
memset(vis, false, sizeof(vis)); // 重置访问标记
cnt = sum; // 设置需要检查的 '+' 数量
// 检查连通性和无回路
if (check(sum_x, sum_y, -1, -1) && !cnt) {
memcpy(answer, mp, sizeof(mp)); // 保存当前最优解
ans = sum; // 更新最优解数量
}
return;
}
// 如果当前格子是 '.',尝试布线
if (mp[x][y] == '.') {
mp[x][y] = '+'; // 布线
memset(vis, false, sizeof(vis)); // 重置访问标记
cnt = sum + 1; // 设置需要检查的 '+' 数量
// 检查连通性和无回路
if (check(x, y, -1, -1)) {
dfs(x, y + 1, sum + 1); // 递归搜索下一个格子
}
mp[x][y] = '.'; // 回溯,取消布线
}
// 不布线的情况
dfs(x, y + 1, sum);
}
int main() {
// 输入网格
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> mp[i][j];
if (mp[i][j] == '+') {
cur++; // 统计初始 '+' 数量
sum_x = i; // 记录初始 '+' 的坐标
sum_y = j;
}
answer[i][j] = mp[i][j]; // 初始化最优解
}
}
// 开始深度优先搜索
dfs(1, 1, cur);
// 输出最优解
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cout << answer[i][j];
}
cout << endl;
}
return 0;
}
无注释版:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 17;
const int dx[] = {0, 1, 0, -1};
const int dy[] = {1, 0, -1, 0};
int n, m;
int cur, cnt;
char mp[MAXN][MAXN];
char answer[MAXN][MAXN];
int sum_x, sum_y;
bool vis[MAXN][MAXN];
int ans = 0;
inline int f(int x, int y)
{
return n * m - (x - 1) * m - y;
}
inline bool check(int x, int y, int x_x, int y_y)
{
if (vis[x][y])
{
return false;
}
vis[x][y] = true;
cnt--;
for (int i = 0; i < 4; i++)
{
int nx = x + dx[i], ny = y + dy[i];
if (x_x == nx && y_y == ny)
{
continue;
}
if (1 <= nx && nx <= n && 1 <= ny && ny <= m && mp[nx][ny] == '+')
{
if (!check(nx, ny, x, y))
{
return false;
}
}
}
return true;
}
inline void dfs(int x, int y, int sum)
{
if (y > m)
{
x++;
y = 1;
}
if (f(x, y) + sum <= ans)
{
return;
}
if (x > n)
{
memset(vis, false, sizeof(vis));
cnt = sum;
if (check(sum_x, sum_y, -1, -1) && !cnt)
{
memcpy(answer, mp, sizeof(mp));
ans = sum;
}
return;
}
if (mp[x][y] == '.')
{
mp[x][y] = '+';
memset(vis, false, sizeof(vis));
cnt = sum + 1;
if (check(x, y, -1, -1))
{
dfs(x, y + 1, sum + 1);
}
mp[x][y] = '.';
}
dfs(x, y + 1, sum);
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cin >> mp[i][j];
if (mp[i][j] == '+')
{
cur++;
sum_x = i;
sum_y = j;
}
answer[i][j] = mp[i][j];
}
}
dfs(1, 1, cur);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cout << answer[i][j];
}
cout << endl;
}
return 0;
}

浙公网安备 33010602011771号