题目链接:https://www.luogu.com.cn/problem/P12135
鼠鼠刚开始想用深度遍历的思路来写,结果写了半天写不出来,一问哈机米说我是错的,应该用dp(动态规划)的思路来写,接下来我就介绍一下哈吉米的思路:
他这道题相当于一个一笔画,就是从最左边有"#"的开始一笔联通到最右边那个"#"。

对于每一列,他只会出现三种情况(在完成任务,即所有都联通后),情况0:只有上面有"#",情况1:"只有下面有#",情况2:"上下都有#",那出现情况0的最小代价是多少呢:那就是min(前一列情况0的状态数,前一列情况2的状态数)+这个格子的情况(如果这个格子是",",那么我们要把它填成"#",代价为1,如果本身是"#",那么代价就是0,不用填了),但是并不是每次三种情况都能成立的,如果这一列的下面是"#",那么就不可能出现情况0了,因为下面那个是必然联通的。
依据这样的思路,代码如下:
cpp
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
const int INF = 1e9; // 无穷大,表示不可达状态
int main() {
// 优化输入输出速度
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
string a, b;
if (!(cin >> a >> b)) return 0;
int n = a.size();
// 1. 找到最左边(L)和最右边(R)包含 '#' 的列
int L = n, R = -1;
for (int i = 0; i < n; i++) {
if (a[i] == '#' || b[i] == '#') {
L = min(L, i);
R = max(R, i);
}
}
// 如果整个河床都没有 '#',直接输出 0
if (R == -1) {
cout << 0 << "\n";
return 0;
}
// dp 数组:dp[0] 上方连通, dp[1] 下方连通, dp[2] 上下都连通
vector<int> dp(3, INF);
// 2. 初始化最左侧起点列 L 的状态
int c0 = (a[L] == '.' ? 1 : 0); // 顶格若为 '.' 则需要放置 1 个代价
int c1 = (b[L] == '.' ? 1 : 0); // 底格若为 '.' 则需要放置 1 个代价
int t0 = (a[L] == '#'); // 顶格是否本身就是目标
int t1 = (b[L] == '#'); // 底格是否本身就是目标
// 如果底格不是必须连接的目标,我们才能只在上方连通
if (!t1) dp[0] = c0;
// 如果顶格不是必须连接的目标,我们才能只在下方连通
if (!t0) dp[1] = c1;
// 上下都连通的状态始终合法
dp[2] = c0 + c1;
// 3. 从 L+1 列推导到 R 列
for (int i = L + 1; i <= R; i++) {
int nc0 = (a[i] == '.' ? 1 : 0);
int nc1 = (b[i] == '.' ? 1 : 0);
int nt0 = (a[i] == '#');
int nt1 = (b[i] == '#');
vector<int> next_dp(3, INF);
// 状态 0:只延续上方。前提是当前列的下方不是必须连接的 '#'
if (!nt1) {
next_dp[0] = min(dp[0], dp[2]) + nc0;
}
// 状态 1:只延续下方。前提是当前列的上方不是必须连接的 '#'
if (!nt0) {
next_dp[1] = min(dp[1], dp[2]) + nc1;
}
// 状态 2:当前列上下都连通。前面的状态只要有一个合法就能转移过来
next_dp[2] = min({dp[0], dp[1], dp[2]}) + nc0 + nc1;
// 滚动更新 dp 数组
dp = next_dp;
}
// 4. 到达最右侧后,三种状态里的最小值就是答案
cout << min({dp[0], dp[1], dp[2]}) << "\n";
return 0;
}