Domino - 题解
标签与难度
标签: 构造, 势能分析, 前缀和, 网格图, 思维题 难度: 2100
题目大意喵~
你好呀,指挥官!这道题是关于多米诺骨牌的喵~
我们有一个 的大棋盘,它被 (横向) 和 (纵向) 的多米诺骨牌完美地铺满了。我们可以对这个棋盘做两种神奇的操作:
- 在一个 的小方格里,如果它正好被两个横向的骨牌覆盖,我们可以把它们旋转一下,变成两个纵向的骨牌。
- 同样,在一个 的小方格里,如果它被两个纵向的骨牌覆盖,我们也可以把它们旋转成两个横向的骨牌。
就像这样,喵~:
-- ||
-- <=> ||现在我们有两份铺好的 棋盘,初始状态(A)和目标状态(B)。问题是,我们最少需要多少次这样的旋转操作,才能把初始状态的棋盘变得和目标状态一模一样呢?
解题思路分析
这道题看起来有点棘手,因为它涉及状态转换和最少操作次数,可能会让人想到复杂的搜索算法,但其实有更巧妙的解法哦,喵~ 让我们一起用猫咪的直觉来破解它吧!
首先,把问题简化一下。将棋盘 A 变成棋盘 B,等价于将一个“差异”棋盘(A 相对于 B 的不同之处)变成一个“全同”棋盘(没有差异)。每一次操作,我们都是在改变局部骨牌的布局。我们的目标是找到一个衡量“差异”的量,并且这个量在每次操作后都会有非常规律的变化。这种方法,在算法竞赛中叫做势能分析,是不是听起来很酷?
让我们来构造一个“势能函数”吧!
1. 关注点分离
一个操作会同时影响横向和纵向的骨牌数量。为了简化问题,我们只关注一种骨牌,比如说,横向的。一个操作可以看作是:在某个 区域内,要么减少两个横向骨牌(同时增加两个纵向骨牌),要么增加两个横向骨牌(同时减少两个纵向骨牌)。
2. 定义差异场
我们创建一个新的 的网格,叫做 diff 好了。对于每个单元格 (i, j),我们记录两个棋盘在这里横向骨牌的差异。
h_A(i, j): 如果棋盘 A 在(i, j)位置有一个横向骨牌的左半部分,则为 1,否则为 0。h_B(i, j): 如果棋盘 B 在(i, j)位置有一个横向骨牌的左半部分,则为 1,否则为 0。diff[i][j] = h_A(i, j) - h_B(i, j)。这个值只可能是 -1, 0, 1。
现在,我们的目标就是通过操作,把整个 diff 网格变成全 0。
3. 寻找“势”
一个在 (r, c)( 方格的左上角)的操作,会影响到 (r, c) 和 (r+1, c) 两处的横向骨牌。
- 如果是
横 -> 竖,那么h_A(r, c)和h_A(r+1, c)都从 1 变为 0。diff[r][c]和diff[r+1][c]都减少了 1。 - 如果是
竖 -> 横,那么h_A(r, c)和h_A(r+1, c)都从 0 变为 1。diff[r][c]和diff[r+1][c]都增加了 1。
每次操作影响了两个位置,还是有点麻烦。能不能让它只影响一个位置呢?
这里就是魔法发生的地方啦!我们引入一个交替的符号。让我们定义一个新的势能场 potential_diff:
现在再看看一次操作的影响。假设在 (r, c) 进行一次 横 -> 竖 的操作:
potential_diff[r][c]的变化量是:potential_diff[r+1][c]的变化量是:
注意到 了吗?喵!这意味着,如果 potential_diff[r][c] 减少了 v,那么 potential_diff[r+1][c] 就会减少 -v,也就是增加了 v!一个位置减少,另一个位置就增加相同的量。
4. 前缀和的魔法
一个加一个减,这让我们想到了什么?对啦,是前缀和!
我们来构造最终的势能场 P,它是 potential_diff 在列方向上的前缀和:
现在,让我们再来看看在 (r, c) 进行一次操作对 P 的影响:
- 对于任意
k < r,P[k][j]不变,因为potential_diff在这些行都没有变化。 - 对于
k = r,P[r][j]的变化量是 potential_diff[r][c] 的变化量,即 。我们称之为 v。 - 对于任意
k > r,P[k][j]的变化量是potential_diff[r][c]的变化量加上potential_diff[r+1][c]的变化量,也就是v + (-v) = 0!
太神奇了,喵!一次操作,只会让势能场 P 中的一个元素 P[r][c] 的值改变 +1 或者 -1。
5. 计算最终答案
我们的目标是让两个棋盘相同,这等价于让所有的 diff[i][j] 都为 0,从而所有的 potential_diff[i][j] 和 P[i][j] 也都为 0。
如果 P[r][c] 的当前值是 X,那么为了把它变成 0,我们需要进行 |X| 次能让 P[r][c] 的绝对值减 1 的操作。因为每次操作只影响一个 P 值,并且是 +/- 1 的变化,所以不同位置的操作是相互独立的。
所以,总的最少操作次数就是把所有 P[i][j] 都变成 0 所需操作次数的总和,也就是:
总结一下我们的算法步骤:
- 创建一个 的
potential_diff数组,全部初始化为 0。 - 遍历初始棋盘 A,如果
(i, j)是一个横向骨牌的左半部分,就给potential_diff[i][j]加上(i % 2 == 0 ? 1 : -1)。 - 遍历目标棋盘 B,如果
(i, j)是一个横向骨牌的左半部分,就从potential_diff[i][j]减去(i % 2 == 0 ? 1 : -1)。 - 对
potential_diff数组的每一列做前缀和,得到最终的势能场P。 - 将
P数组中所有元素的绝对值相加,就是我们想要的答案啦!
代码实现
这是我根据上面的思路,精心为你准备的代码哦~ 喵~
#include <iostream>
#include <vector>
#include <string>
#include <cmath>
// 使用一个函数来处理输入和计算,让主函数更整洁喵~
void solve() {
int n, m;
// 持续读取输入直到文件末尾
while (std::cin >> n >> m) {
// 为了方便处理,我们多留一些空间,避免边界检查的麻烦
std::vector<std::vector<int>> potential_diff(n + 1, std::vector<int>(m + 1, 0));
std::vector<std::string> grid(n);
// --- 步骤 1 & 2: 处理初始棋盘 A ---
for (int i = 0; i < n; ++i) {
std::cin >> grid[i];
for (int j = 0; j < m; ++j) {
// 我们只关心横向骨牌的左半部分
if (grid[i][j] == '-') {
// 根据行号的奇偶性给予不同的符号
// (i % 2 == 0 ? 1 : -1) 和 ( (i & 1) ? -1 : 1 ) 是一样的哦
int sign = (i % 2 == 0) ? 1 : -1;
potential_diff[i][j] += sign;
// 跳过横向骨牌的右半部分,防止重复计算
j++;
}
}
}
// --- 步骤 3: 处理目标棋盘 B ---
for (int i = 0; i < n; ++i) {
std::cin >> grid[i];
for (int j = 0; j < m; ++j) {
if (grid[i][j] == '-') {
int sign = (i % 2 == 0) ? 1 : -1;
potential_diff[i][j] -= sign; // 从差异中减去
j++;
}
}
}
// --- 步骤 4: 计算列方向的前缀和 ---
// 这会把 potential_diff 数组变成我们最终的势能场 P
for (int j = 0; j < m; ++j) {
for (int i = 1; i < n; ++i) {
potential_diff[i][j] += potential_diff[i - 1][j];
}
}
// --- 步骤 5: 累加势能场所有值的绝对值 ---
long long total_ops = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
total_ops += std::abs(potential_diff[i][j]);
}
}
std::cout << total_ops << std::endl;
}
}
int main() {
// 加速一下输入输出,让程序跑得更快,像猫咪一样敏捷!
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
solve();
return 0;
}复杂度分析
时间复杂度: 我们对两个 的网格进行了几次完整的遍历:读取输入、构建
potential_diff数组、计算前缀和、以及最后求和。每一步都是线性的,所以总时间复杂度是 ,非常高效的说!空间复杂度: 我们主要使用了一个 大小的
potential_diff数组来存储我们的势能场,以及一个grid数组来读取输入。所以额外空间是 。
知识点总结
这道题真是一次有趣的思维探险,对吧?我们可以从中学习到:
- 势能分析 (Potential Function Method): 这是解决这类状态转换问题的强大武器。通过构造一个巧妙的“势能”函数,使得每次操作对势能的影响变得极其简单和局部化,从而将复杂问题转化为简单的计数问题。
- 前缀和 (Prefix Sum): 前缀和不仅能用于快速求区间和,还能像这道题一样,作为一种变换工具,将成对的、符号相反的变化(一个
+v,一个-v)转化为单点变化,这是解题的关键一步。 - 构造性思维 (Constructive Thinking): 面对没有明显模板可以套用的问题时,深入分析操作的性质,尝试构造出能反映问题本质的数学模型,是通往解法的重要途径。
希望这篇题解能帮到你,指挥官!如果还有其他问题,随时可以来找我玩哦,喵~