首先,你选择一个方向:上、下、左、右。
然后,把所有的银币往你选择的方向移动一格。如果这个操作会使某些银币越出了矩阵的界,那么越界的银币会自动消失。
你现在的任务是:用最少的操作次数,使得矩阵里剩下的银币数量恰好是K,输出最少的操作次数。如果不可能完成任务,输出-1.
输入格式
第一行,三个整数R、C、K。1 <= R,C <= 30. 1<=K<=900.
接下来是R行C列的矩阵。
输出格式
一个整数。
输入/输出例子1
输入:
3 4 3
.o..
oooo
..o.
输出:
2
输入/输出例子2
输入:
6 6 12
.....o
......
oooooo
oooooo
......
o.....
输出:
3
样例解释
样例一解释:
其中一种最优方案是
向右移动两次
样例二解释:
其中一种最优方案是
向上移动
向下移动
向下移动
浅说:小假对本题进行了详细地讲解,包括思路,核心操作和样例,方便大家理解,完整代码在最后面~
思路:这个代码通过 二维前缀和 和 枚举矩形区域 的方法来解决这个问题。它的核心思想是找到一个矩形区域,使得该区域内的银币数量恰好为 K
,并计算将该区域外的银币移出矩阵所需的最少操作次数
输入和初始化
cin >> r >> c >> k;
for (int i = 1; i <= r; ++i) {
cin >> a[i] + 1;
}
-
输入矩阵的行数
r
、列数c
和目标银币数量k
-
输入矩阵
a
,其中a[i][j]
表示第i
行第j
列的格子是'o'
(银币)还是'.'
(空)
二维前缀和预处理
void f1() {
for (int i = 1; i <= r; ++i) {
for (int j = 1; j <= c; ++j) {
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + (a[i][j] == 'o');
}
}
}
-
s[i][j]
表示从(1, 1)
到(i, j)
的矩形区域内银币的总数 -
通过动态规划计算二维前缀和:
-
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + (a[i][j] == 'o')
-
这里
(a[i][j] == 'o')
是一个布尔表达式,值为1
(如果a[i][j]
是银币)或0
(否则)
-
计算矩形区域内的银币数量
int f2(int x1, int y1, int x2, int y2) {
return s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
}
-
f2(x1, y1, x2, y2)
计算从(x1, y1)
到(x2, y2)
的矩形区域内的银币数量。 -
公式为:
-
s[x2][y2]
:从(1, 1)
到(x2, y2)
的总银币数 -
减去
s[x1 - 1][y2]
:从(1, 1)
到(x1 - 1, y2)
的总银币数 -
减去
s[x2][y1 - 1]
:从(1, 1)
到(x2, y1 - 1)
的总银币数 -
加上
s[x1 - 1][y1 - 1]
:从(1, 1)
到(x1 - 1, y1 - 1)
的总银币数(因为被减了两次,需要加回来)
-
核心操作:枚举所有可能的矩形区域
int ans = INT_MAX;
for (int x1 = 1; x1 <= r; ++x1) {
for (int y1 = 1; y1 <= c; ++y1) {
for (int x2 = x1; x2 <= r; ++x2) {
for (int y2 = y1; y2 <= c; ++y2) {
if (f2(x1, y1, x2, y2) == k) {
int moves = (x1 - 1) + (y1 - 1) + (r - x2) + (c - y2);
moves += min(x1 - 1, r - x2);
moves += min(y1 - 1, c - y2);
ans = min(ans, moves);
}
}
}
}
}
-
枚举所有可能的矩形区域
(x1, y1)
到(x2, y2)
-
对于每个矩形区域,检查其银币数量是否等于
k
:-
如果是,则计算将该矩形区域外的银币移出矩阵所需的最少操作次数
-
-
操作次数的计算:
-
(x1 - 1)
:将矩形区域上方的银币向上移出矩阵 -
(y1 - 1)
:将矩形区域左侧的银币向左移出矩阵 -
(r - x2)
:将矩形区域下方的银币向下移出矩阵 -
(c - y2)
:将矩形区域右侧的银币向右移出矩阵 -
min(x1 - 1, r - x2)
:将矩形区域上方或下方的银币移出矩阵的额外操作次数(取最小值) -
min(y1 - 1, c - y2)
:将矩形区域左侧或右侧的银币移出矩阵的额外操作次数(取最小值)
-
-
更新最小操作次数
ans
输出结果
if (ans == INT_MAX) {
cout << -1 << endl;
} else {
cout << ans << endl;
}
-
如果
ans
仍然是INT_MAX
,说明没有找到符合条件的矩形区域,输出-1
。 -
否则,输出最小操作次数
ans
示例解释
初始银币分布:
. o . .
o o o o
. . o .
-
通过枚举矩形区域,找到一个银币数量为
3
的区域,例如(1, 2)
到(3, 3)
-
计算操作次数:
-
将上方和左侧的银币移出矩阵,操作次数为
2
-
-
输出结果为
2
完整代码
#include <bits/stdc++.h>
using namespace std;
int r, c, k,s[35][35];
char a[35][35];
void f1() {
for (int i = 1; i <= r; ++i) {
for (int j = 1; j <= c; ++j) {
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + (a[i][j] == 'o');
}
}
}
int f2(int x1, int y1, int x2, int y2) {
return s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
}
int main() {
cin >> r >> c >> k;
for (int i = 1; i <= r; ++i) {
cin >> a[i] + 1;
}
f1();
int ans = INT_MAX;
for (int x1 = 1; x1 <= r; ++x1) {
for (int y1 = 1; y1 <= c; ++y1) {
for (int x2 = x1; x2 <= r; ++x2) {
for (int y2 = y1; y2 <= c; ++y2) {
if (f2(x1, y1, x2, y2) == k) {
int moves = (x1 - 1) + (y1 - 1) + (r - x2) + (c - y2);
moves += min(x1 - 1, r - x2);
moves += min(y1 - 1, c - y2);
ans = min(ans, moves);
}
}
}
}
}
if (ans == INT_MAX) {
cout << -1 << endl;
} else {
cout << ans << endl;
}
return 0;
}