小红的矩阵染色
思路
题目说,红色格子的正下方如果也是红色格子就得 1 分。换句话说,我们需要在同一列中找到尽可能多的"上下相邻红色对"。
先想一个简单问题——如果只有一列,全是 o,长度为 L,我花 l 个格子全染在一起,能得几分?
> l 个连续红色格子,相邻对数是 l - 1。
那如果这一列中间被 隔断了呢?比如 o o o o o,它就被拆成了两段:长度 2 和长度 3。跨段的格子之间被黑色隔开了,不可能形成相邻对,所以每一段是独立的。
这样问题就变成了:收集所有列中所有连续 o 段,每段长度为 L,从中选若干段,总共用不超过 k 个格子,使得得分最大。
对于一个长度为 L 的段,花 l 个格子()可以得到
分。拆解一下边际收益:
- 第 1 个格子:花 1 格,得 0 分(孤立一个红色没用)
- 第 2 个格子:花 1 格,得 1 分(和第 1 个组成一对)
- 第 3、4、... 个格子:每花 1 格,得 1 分
也就是说,启动一个段需要 2 格才能拿到第 1 分,之后每 1 格就多 1 分。那贪心策略就出来了:
> 应该优先把格子花在长段上,因为同样的"启动成本"(2 格换 1 分),长段能提供更多的"1 格换 1 分"的后续收益。
具体步骤
- 遍历每一列,提取所有连续
o段,记录长度(长度 < 2 的没用,丢掉) - 把所有段按长度从大到小排序
- 贪心分配 k 个格子:对每个段,用
个格子,得
分。剩余格子不足 2 就停止
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int m, n, k;
scanf("%d%d%d", &m, &n, &k);
vector<string> grid(m);
for (int i = 0; i < m; i++) {
cin >> grid[i];
}
// 收集每列中所有连续 'o' 段的长度
vector<int> segs;
for (int j = 0; j < n; j++) {
int cnt = 0;
for (int i = 0; i < m; i++) {
if (grid[i][j] == 'o') {
cnt++;
} else {
if (cnt >= 2) segs.push_back(cnt);
cnt = 0;
}
}
if (cnt >= 2) segs.push_back(cnt);
}
// 长段优先
sort(segs.begin(), segs.end(), greater<int>());
int score = 0, rem = k;
for (int len : segs) {
if (rem < 2) break;
int use = min(rem, len);
score += use - 1;
rem -= use;
}
printf("%d\n", score);
return 0;
}
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int m = sc.nextInt(), n = sc.nextInt(), k = sc.nextInt();
String[] grid = new String[m];
for (int i = 0; i < m; i++) {
grid[i] = sc.next();
}
List<Integer> segs = new ArrayList<>();
for (int j = 0; j < n; j++) {
int cnt = 0;
for (int i = 0; i < m; i++) {
if (grid[i].charAt(j) == 'o') {
cnt++;
} else {
if (cnt >= 2) segs.add(cnt);
cnt = 0;
}
}
if (cnt >= 2) segs.add(cnt);
}
segs.sort(Collections.reverseOrder());
int score = 0, rem = k;
for (int len : segs) {
if (rem < 2) break;
int use = Math.min(rem, len);
score += use - 1;
rem -= use;
}
System.out.println(score);
}
}
import sys
input = sys.stdin.readline
m, n, k = map(int, input().split())
grid = [input().strip() for _ in range(m)]
segs = []
for j in range(n):
cnt = 0
for i in range(m):
if grid[i][j] == 'o':
cnt += 1
else:
if cnt >= 2:
segs.append(cnt)
cnt = 0
if cnt >= 2:
segs.append(cnt)
segs.sort(reverse=True)
score = 0
rem = k
for length in segs:
if rem < 2:
break
use = min(rem, length)
score += use - 1
rem -= use
print(score)
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin });
const lines = [];
rl.on('line', line => lines.push(line));
rl.on('close', () => {
const [m, n, k] = lines[0].split(' ').map(Number);
const grid = [];
for (let i = 1; i <= m; i++) grid.push(lines[i]);
const segs = [];
for (let j = 0; j < n; j++) {
let cnt = 0;
for (let i = 0; i < m; i++) {
if (grid[i][j] === 'o') {
cnt++;
} else {
if (cnt >= 2) segs.push(cnt);
cnt = 0;
}
}
if (cnt >= 2) segs.push(cnt);
}
segs.sort((a, b) => b - a);
let score = 0, rem = k;
for (const len of segs) {
if (rem < 2) break;
const use = Math.min(rem, len);
score += use - 1;
rem -= use;
}
console.log(score);
});
复杂度分析
- 时间复杂度:
,其中
为段数,最多
- 空间复杂度:
,存储网格和段列表
总结
这道题的核心是把二维矩阵问题转化成一维段的贪心选择。每一列被 * 隔开的连续 o 段是独立的,对于每个段花 l 个格子能得 l-1 分。由于"启动"一个段需要 2 个格子才能拿到第 1 分,所以我们应该优先把有限的 k 个格子投入到最长的段里,榨干每个段的收益后再开辟新段。排序 + 贪心分配,简洁明了。



京公网安备 11010502036488号