把 L 形棋盘按黑白染色后,任意一次操作都会同时改动一黑一白,因此带符号和不变,可直接解出缺失值。
OJ: luogu
题目 ID: P8050
难度:普及+/提高
标签:图论二分图思维
日期: 2026-06-18 21:58
题意
给出一个 L 形棋盘。初始时所有格子的值都等于 k。
每次操作可以选择两个相邻格子,把它们同时 +1 或同时 -1。
现在给出若干次操作后的棋盘,但其中恰好有一个位置被写成 999999。
题目要我们求出这个缺失格子的真实值。
思路
先看一个可以直接验证想法的朴素解:
因为题目保证最终每个格子的值都在 [-1000,1000],所以可以直接枚举缺失值 x:
- 把
x填回去; - 计算整张棋盘的“黑格和减白格和”;
- 如果它和初始棋盘一致,那么这个
x就是答案。
cpp
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 205;
int n1, m1, n2, m2, k;
int top_rows;
int total_rows;
int a[MAXN][MAXN];
int miss_x, miss_y;
int row_len(int row) {
if (row <= top_rows) {
return m1;
}
return m2;
}
int cell_sign(int row, int col) {
if ((row + col) % 2 == 0) {
return 1;
}
return -1;
}
void read_input() {
cin >> n1 >> m1 >> n2 >> m2 >> k;
if (n1 > 0 && m1 > 0) {
top_rows = n1;
} else {
top_rows = 0;
}
total_rows = top_rows + n2;
miss_x = miss_y = 0;
for (int i = 1; i <= total_rows; i++) {
int len = row_len(i);
for (int j = 1; j <= len; j++) {
cin >> a[i][j];
if (a[i][j] == 999999) {
miss_x = i;
miss_y = j;
}
}
}
}
// 直接枚举缺失值,检查黑白带符号和是否和初始棋盘一致。
bool check_value(int value) {
ll sum = 0;
for (int i = 1; i <= total_rows; i++) {
int len = row_len(i);
for (int j = 1; j <= len; j++) {
int cur = a[i][j];
if (i == miss_x && j == miss_y) {
cur = value;
}
sum += 1LL * cell_sign(i, j) * cur;
}
}
ll target = 0;
for (int i = 1; i <= total_rows; i++) {
int len = row_len(i);
for (int j = 1; j <= len; j++) {
target += 1LL * cell_sign(i, j) * k;
}
}
return sum == target;
}
void solve() {
for (int value = -1000; value <= 1000; value++) {
if (check_value(value)) {
cout << value << '\n';
return;
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
read_input();
solve();
return 0;
}关键在于,为什么只检查这个带符号和就够了?
先把棋盘像普通国际象棋一样黑白染色。对样例来说,可以给每个格子赋这样的符号:
| 行\列 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|
| 1 | + |
- |
× |
× |
| 2 | - |
+ |
× |
× |
| 3 | + |
- |
+ |
- |
| 4 | - |
+ |
- |
+ |
| 5 | + |
- |
+ |
- |
这里 × 表示这一格不存在。
由于每次操作总是作用在两个相邻格子上,而相邻格子的符号一定一正一负,所以一次操作对
S = 所有格子的符号 × 数值之和
的影响永远是 0。
这就说明 S 是不变量。
初始时所有格子都等于 k,所以初始不变量 S0 可以直接算出来。
设缺失位置的符号是 sgn,其他已知格子的带符号和是 sum_known,那么最后状态必须满足:
sum_known + sgn × x = S0
于是直接解得:
x = sgn × (S0 - sum_known)
正式代码就是按这个式子一步算出答案。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 205;
int n1, m1, n2, m2, k;
int top_rows;
int total_rows;
int a[MAXN][MAXN];
int miss_x, miss_y;
int row_len(int row) {
if (row <= top_rows) {
return m1;
}
return m2;
}
int cell_sign(int row, int col) {
if ((row + col) % 2 == 0) {
return 1;
}
return -1;
}
void read_input() {
cin >> n1 >> m1 >> n2 >> m2 >> k;
// 上面的小矩形只在 n1,m1 都大于 0 时才真正存在。
if (n1 > 0 && m1 > 0) {
top_rows = n1;
} else {
top_rows = 0;
}
total_rows = top_rows + n2;
miss_x = miss_y = 0;
for (int i = 1; i <= total_rows; i++) {
int len = row_len(i);
for (int j = 1; j <= len; j++) {
cin >> a[i][j];
if (a[i][j] == 999999) {
miss_x = i;
miss_y = j;
}
}
}
}
void solve() {
ll target = 0; // 初始棋盘的黑白带符号和
ll known_sum = 0; // 除缺失值外,当前棋盘的带符号和
for (int i = 1; i <= total_rows; i++) {
int len = row_len(i);
for (int j = 1; j <= len; j++) {
int sgn = cell_sign(i, j);
target += 1LL * sgn * k;
if (i == miss_x && j == miss_y) {
continue;
}
known_sum += 1LL * sgn * a[i][j];
}
}
int sgn = cell_sign(miss_x, miss_y);
ll ans = 1LL * sgn * (target - known_sum);
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
read_input();
solve();
return 0;
}复杂度
设棋盘总格子数为 N,正式做法只扫描一遍棋盘,所以时间复杂度是
总结
这题不是去还原操作过程,而是去找“操作不改变什么”。
一旦看出棋盘相邻关系是二分图,黑白染色后的带符号和就是天然不变量,缺失值也就能直接列式算出来了。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
