把每个 5x9 窗口转成阈值区间,用差分数组合并所有能呈现 CSP 水印的阈值。
OJ: shumeng
题目 ID: CSP202509B
难度:普及-
标签:枚举差分二维数组
日期: 2026-07-31 16:21
形式化题目
给定
思路
先看最直接的做法:对每个阈值、每个窗口逐一比对。
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-31 16:21
* update_at: 2026-08-17 23:00
*/
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, limit;
cin >> n >> limit;
vector<vector<int> > image(n, vector<int>(n));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) cin >> image[i][j];
}
int pattern[5][9] = {
{1, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 1, 0, 0, 1, 0, 1},
{1, 0, 0, 1, 1, 1, 1, 1, 0},
{1, 0, 0, 0, 0, 1, 1, 0, 0},
{1, 1, 1, 1, 1, 1, 1, 0, 0},
};
// 朴素做法:对每个阈值 k 逐窗口检查是否匹配水印图案,适合小数据对拍
for (int k = 0; k < limit; k++) {
bool found = false;
for (int top = 0; top + 5 <= n && !found; top++) {
for (int left = 0; left + 9 <= n && !found; left++) {
bool valid = true;
for (int i = 0; i < 5 && valid; i++) {
for (int j = 0; j < 9; j++) {
bool white = image[top + i][left + j] >= k;
if ((int)white != pattern[i][j]) {
valid = false;
break;
}
}
}
if (valid) found = true;
}
}
if (found) cout << k << '\n';
}
return 0;
}关键观察:阈值形成连续区间
固定一个
差分合并
每个窗口只贡献一个区间。枚举所有窗口,用差分数组合并这些区间,最后扫描
代码
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-31 16:21
* update_at: 2026-08-17 23:00
*/
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, limit;
cin >> n >> limit;
vector<vector<int> > image(n, vector<int>(n)); // 灰度图像
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) cin >> image[i][j];
}
// CSP 水印图案:1 表示白色(灰度>=k),0 表示黑色(灰度<k)
int pattern[5][9] = {
{1, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 1, 0, 0, 1, 0, 1},
{1, 0, 0, 1, 1, 1, 1, 1, 0},
{1, 0, 0, 0, 0, 1, 1, 0, 0},
{1, 1, 1, 1, 1, 1, 1, 0, 0},
};
// 差分数组:difference[k] 表示阈值 k 的覆盖次数变化量
vector<int> difference(limit + 2, 0);
for (int top = 0; top + 5 <= n; top++) {
for (int left = 0; left + 9 <= n; left++) {
// 该窗口能呈现水印的阈值区间:[黑色位置最大灰度+1, 白色位置最小灰度]
int minimum_white = limit;
int maximum_black = -1;
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 9; j++) {
if (pattern[i][j] == 1) {
minimum_white = min(minimum_white, image[top + i][left + j]);
} else {
maximum_black = max(maximum_black, image[top + i][left + j]);
}
}
}
int lower = maximum_black + 1;
int upper = minimum_white;
if (lower <= upper) {
difference[lower]++;
difference[upper + 1]--;
}
}
}
// 前缀和求每个阈值被覆盖的次数,大于 0 即可检测出水印
int active = 0;
for (int k = 0; k < limit; k++) {
active += difference[k];
if (active > 0) cout << k << '\n';
}
return 0;
}复杂度
窗口数为
总结
阈值对固定窗口形成一个连续区间,先求区间再统一差分,比逐阈值检查每个窗口快得多。本题也可以先对所有窗口收集区间,再用扫描线求并,差分是最简单的实现。
