题解:洛谷 P1671 Rigging the Bovine Election
【题目来源】
洛谷:P1671 [USACO05FEB] Rigging the Bovine Election S - 洛谷
【题目描述】
农场被划分为 \(5\times 5\) 的格子,每个格子中都有一头奶牛,并且只有荷斯坦(标记为 H)和杰西(标记为 J)两个品种。如果一头奶牛在另一头上下左右四个格子中的任一格里,我们说它们相连。奶牛要大选了。现在杰西奶牛们想选择 \(7\) 头相连的奶牛,划成一个竞选区,使得其中它们品种的奶牛比荷斯坦的多。
要求你编写一个程序求出方案总数。
【输入】
\(5\) 行,表示农场的情况。
【输出】
输出划区方案总数。
【输入样例】
HHHHH
JHJHJ
HHHHH
HJHHJ
HHHHH
【输出样例】
2
【核心思想】
-
问题分析:给定 \(5 \times 5\) 的网格,每个格子为
H或J。需要找出所有由 \(7\) 个相连格子组成的连通块,使得其中J的数量 \(> 3\)(即至少 \(4\) 个)。求满足条件的连通块方案总数。这是一个DFS搜索连通块 + 去重统计问题,关键在于如何枚举所有大小为 \(7\) 的连通块并避免重复计数。 -
算法选择:
- DFS扩展连通块:从每个起点出发,逐步向当前连通块的相邻格子扩展,维护当前路径和
J的计数 - 路径排序去重:由于同一连通块可能从不同起点、不同扩展顺序被多次发现,将路径坐标排序后转为字符串,用
set自动去重 - 连通性保证:每次扩展时只能从当前已选格子的邻域中选择新格子,确保最终 \(7\) 个格子形成连通块
- DFS扩展连通块:从每个起点出发,逐步向当前连通块的相邻格子扩展,维护当前路径和
-
关键步骤:
- 读取输入:\(5 \times 5\) 的字符矩阵 \(a\)
- DFS搜索(从每个起点 \((i, j)\) 开始):
- 参数:当前坐标 \((x, y)\),
J的计数 \(cnt\),已选格子数 \(tot\) - 将 \((x, y)\) 加入当前路径 \(v\)
- 若 \(tot = 7\):
- 若 \(cnt > 3\):将路径坐标排序后转字符串,插入
set去重 - 回溯弹出,返回
- 若 \(cnt > 3\):将路径坐标排序后转字符串,插入
- 标记 \(vis[x][y] = 1\)
- 扩展策略:遍历当前路径 \(v\) 中所有格子的四邻域,选择未访问且在界内的格子递归
- 回溯:弹出 \((x, y)\),\(vis[x][y] = 0\)
- 参数:当前坐标 \((x, y)\),
- 输出
st.size()
-
时间/空间复杂度:
- 时间复杂度:\(O(25 \times C_{24}^{6} \times 7 \log 7)\),从 \(25\) 个起点出发,每次扩展选择相邻格子,最坏组合级复杂度,但 \(5 \times 5\) 网格限制实际可接受
- 空间复杂度:\(O(7 \times |st|)\),路径向量 + 去重集合
-
DFS连通块枚举的核心思想:
- 路径记录 + 排序去重:连通块是无序集合,不同搜索顺序可能产生同一连通块。将坐标排序后序列化,利用
set的唯一性保证每个连通块只计数一次 - 从当前连通块扩展:每次新格子必须与已选集合中某格子相邻,而非仅从当前DFS节点扩展,这保证了连通性但增加了搜索分支
- 剪枝隐含:\(cnt\) 计数在搜索过程中自然维护,仅在 \(tot = 7\) 时判断是否满足 \(cnt > 3\)
- 适用于"网格连通块枚举 + 属性约束计数"类问题,核心是避免重复计数和保证连通性
- 路径记录 + 排序去重:连通块是无序集合,不同搜索顺序可能产生同一连通块。将坐标排序后序列化,利用
【解题思路】

【算法标签】
普及 #DFS-二维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// 定义5x5的字符数组
char a[10][10]; // 存储字符矩阵,实际使用1-5行和1-5列
bool vis[10][10]; // 标记数组,记录位置是否被访问过
// 定义坐标对类型
typedef pair<int, int> PII;
vector<PII> v; // 存储当前搜索路径上的坐标
// 方向数组:上、下、左、右
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
// 使用集合存储去重后的路径字符串表示
set<string> st;
// 深度优先搜索函数
// 参数说明:
// x, y: 当前坐标
// cnt: 当前路径中'J'的个数
// tot: 当前路径中的位置总数
void dfs(int x, int y, int cnt, int tot)
{
// 将当前位置加入路径
v.push_back({x, y});
// 如果已经选择了7个位置
if (tot == 7)
{
// 检查'J'的个数是否超过3个
if (cnt > 3)
{
// 创建当前路径的副本并排序
vector<PII> tmp = v;
sort(tmp.begin(), tmp.end());
// 将排序后的路径转换为字符串
string s = "";
for (auto t : tmp)
{
s += to_string(t.first) + "," + to_string(t.second) + ";";
}
// 将字符串加入集合(自动去重)
st.insert(s);
}
// 回溯:移除最后一个位置
v.pop_back();
return;
}
// 标记当前位置为已访问
vis[x][y] = 1;
// 遍历当前路径中的所有位置
for (int i = 0; i < v.size(); i++)
{
int xx = v[i].first, yy = v[i].second;
// 尝试四个方向
for (int j = 0; j < 4; j++)
{
int nx = xx + dx[j], ny = yy + dy[j];
// 跳过已访问的位置
if (vis[nx][ny])
{
continue;
}
// 跳过越界的位置
if (nx < 1 || nx > 5 || ny < 1 || ny > 5)
{
continue;
}
// 递归搜索下一个位置
// 如果新位置是'J',则cnt+1,否则cnt不变
// tot总是+1
dfs(nx, ny, cnt + (a[nx][ny] == 'J'), tot + 1);
}
}
// 回溯:移除当前位置,并取消标记
v.pop_back();
vis[x][y] = 0;
}
int main()
{
// 读入5x5的字符矩阵
for (int i = 1; i <= 5; i++)
{
for (int j = 1; j <= 5; j++)
{
cin >> a[i][j];
}
}
// 从每个位置开始深度优先搜索
for (int i = 1; i <= 5; i++)
{
for (int j = 1; j <= 5; j++)
{
// 初始状态:从位置(i,j)开始
// cnt初始为1如果当前位置是'J'
// tot初始为1
dfs(i, j, a[i][j] == 'J', 1);
}
}
// 输出不同路径的数量
cout << st.size() << endl;
return 0;
}
【运行结果】
HHHHH
JHJHJ
HHHHH
HJHHJ
HHHHH
2
浙公网安备 33010602011771号