银币(详细版)

有一个3030列的矩阵,每个格子是‘o’或者是‘.’。其中前者表示有一枚银币,后者表示没有银币。下面的两个步骤合起来,称为一次操作: 
首先,你选择一个方向:上、下、左、右。 
然后,把所有的银币往你选择的方向移动一格。如果这个操作会使某些银币越出了矩阵的界,那么越界的银币会自动消失。 
你现在的任务是:用最少的操作次数,使得矩阵里剩下的银币数量恰好是K,输出最少的操作次数。如果不可能完成任务,输出-1. 

 

输入格式

第一行,三个整数RCK1 <= R,C <= 30. 1<=K<=900. 
接下来是RC列的矩阵。

输出格式

一个整数。

输入/输出例子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;
}

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小假

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值