
CP Weekly Challenge
CCC 2021 J5/S2 Modern Art Solution: Count Gold Cells with Parity
Learn how to solve Modern Art with parity, row and column toggling, and an O(M + N + K) counting formula in Python and C++.
CP Weekly Challenge
CCC 2021 J5/S2 Modern Art 题解:用奇偶性避开整张画布
用奇偶性记录行列 toggle 状态,不建立整张画布,也能用公式数出所有 gold cells。
This problem looks like a two-dimensional grid simulation. We have an M x N canvas, every cell starts black, and each operation toggles one entire row or one entire column.
But the efficient solution does not build the whole canvas. The key idea is parity: whether each row and column has been toggled an odd or even number of times.
What the Problem Is Asking
Initially, every cell is black.
There are K brush operations:
R x: toggle rowxC y: toggle columny
Toggling reverses the color:
- black becomes gold
- gold becomes black
After all operations, we need to output the number of gold cells.
Where Beginners Often Get Stuck
A natural first attempt is to create the full grid:
grid = [[False] * N for _ in range(M)]
Then each row operation flips every cell in a row, and each column operation flips every cell in a column.
This may work for tiny examples, but it is not the right contest approach. The canvas can be too large to store or update directly.
Instead, ask: what determines the final color of one cell?
The answer is the number of times that cell was toggled.
If a cell is toggled an odd number of times, it ends gold. If it is toggled an even number of times, it ends black.
Core Algorithm Idea
For a cell (r, c), the total number of toggles is:
rowPaintCount[r] + colPaintCount[c]
The cell is affected only by its own row and its own column.
We do not need the exact counts. We only need odd or even.
So we maintain:
row_odd[r] = whether row r was toggled an odd number of times
col_odd[c] = whether column c was toggled an odd number of times
Each R x operation flips row_odd[x]. Each C y operation flips col_odd[y].
State and Counting Formula
Let:
R = number of rows toggled odd times
C = number of columns toggled odd times
A cell is gold exactly when one of its row or column is odd, but not both.
In Boolean terms:
row_odd[r] XOR col_odd[c] == True
There are two gold cases:
- odd row + even column
- even row + odd column
So the total number of gold cells is:
R * (N - C) + (M - R) * C
The first term counts cells in odd rows and even columns. The second term counts cells in even rows and odd columns.
Algorithm Steps
- Read
M,N, andK. - Create
row_oddandcol_odd. - For each operation:
- toggle the row parity for
R x - toggle the column parity for
C y
- Count how many rows are odd.
- Count how many columns are odd.
- Apply the formula:
R * (N - C) + (M - R) * C
Python Solution
M = int(input())
N = int(input())
K = int(input())
row_odd = [False] * (M + 1)
col_odd = [False] * (N + 1)
for _ in range(K):
brush_type, num = input().split()
idx = int(num)
if brush_type == "R":
row_odd[idx] = not row_odd[idx]
else:
col_odd[idx] = not col_odd[idx]
R = sum(row_odd)
C = sum(col_odd)
gold = R * (N - C) + (M - R) * C
print(gold)
C++ Solution
#include <iostream>
#include <vector>
using namespace std;
int main() {
int M, N, K;
cin >> M >> N >> K;
vector<bool> rowOdd(M + 1, false);
vector<bool> colOdd(N + 1, false);
for (int i = 0; i < K; i++) {
char brushType;
int idx;
cin >> brushType >> idx;
if (brushType == 'R') {
rowOdd[idx] = !rowOdd[idx];
} else {
colOdd[idx] = !colOdd[idx];
}
}
long long R = 0;
long long C = 0;
for (int i = 1; i <= M; i++) {
if (rowOdd[i]) R++;
}
for (int j = 1; j <= N; j++) {
if (colOdd[j]) C++;
}
long long gold = R * (N - C) + (M - R) * C;
cout << gold << "\n";
return 0;
}
Complexity Analysis
We avoid building the M x N grid.
Processing the K operations takes:
O(K)
Counting odd rows and columns takes:
O(M + N)
So the total time complexity is:
O(M + N + K)
The memory complexity is:
O(M + N)
Common Mistakes
- Building and updating the full
M x Ncanvas. - Tracking full paint counts when only parity matters.
- Forgetting that toggling the same row twice cancels out.
- Counting cells where both row and column are odd as gold, even though two toggles return them to black.
- Using
intfor the final answer in C++, which may overflow.
Review Questions
- Why does only odd/even toggle count matter?
- Why does painting the same row twice cancel out?
- What does
R * (N - C)count? - What does
(M - R) * Ccount? - Why is building the full grid unnecessary?
这道题最迷惑人的地方,是它看起来像一道二维 grid simulation:有一个 M x N 的画布,每次刷一整行或一整列,最后问有多少格是 gold。
但如果真的把整张画布建出来,然后每次刷一行或一列,数据一大就会很慢。真正的关键不是模拟每个格子,而是抓住一个更小的状态:每一行和每一列被刷了奇数次还是偶数次。
题目要我们做什么
一开始,画布上每个格子都是 black。
接下来有 K 次操作,每次操作有两种可能:
R x: toggle 第x行C y: toggle 第y列
Toggle 的意思是颜色反转:
- black 变 gold
- gold 变 black
最后要输出画布上 gold cell 的数量。
初学者容易卡在哪里
最直接的想法是建一个二维数组:
grid = [[False] * N for _ in range(M)]
然后每次操作都真的把一整行或一整列翻转。
这个思路在小数据上可以工作,但在竞赛题里通常不够好。因为 M 和 N 可能很大,M x N 的画布本身就可能无法存下,更不用说每次操作都扫一行或一列。
这道题要学会问一个更重要的问题:
一个格子的最终颜色,到底由什么决定?
答案是:它被 toggle 的总次数。
如果一个格子被 toggle 奇数次,它是 gold;如果被 toggle 偶数次,它回到 black。
核心算法概念
这道题的核心是 parity,也就是奇偶性。
一个格子 (r, c) 会被 toggle 的次数等于:
rowPaintCount[r] + colPaintCount[c]
因为它只会受到两类操作影响:
- 第
r行被刷了多少次 - 第
c列被刷了多少次
我们其实不需要知道具体刷了 7 次还是 9 次,只需要知道是奇数次还是偶数次。
所以可以维护两个数组:
row_odd[r] = 第 r 行是否被刷了奇数次
col_odd[c] = 第 c 列是否被刷了奇数次
每次遇到 R x,就把 row_odd[x] 反转。每次遇到 C y,就把 col_odd[y] 反转。
状态定义
假设有:
R = 被刷了奇数次的行数
C = 被刷了奇数次的列数
一个格子最终是 gold,当且仅当它所在的行和列中,正好有一个是 odd。
也就是:
row_odd[r] XOR col_odd[c] == True
有两种情况会产生 gold:
- 行是 odd,列是 even
- 行是 even,列是 odd
所以 gold cell 的数量是:
R * (N - C) + (M - R) * C
第一部分 R * (N - C) 表示:选一条 odd row,再选一条 even column。
第二部分 (M - R) * C 表示:选一条 even row,再选一条 odd column。
算法步骤
- 读入
M,N,K。 - 建立
row_odd和col_odd。 - 对每次操作:
- 如果是
R x,反转row_odd[x]。 - 如果是
C y,反转col_odd[y]。
- 统计 odd rows 的数量
R。 - 统计 odd columns 的数量
C。 - 用公式计算答案:
R * (N - C) + (M - R) * C
Python 解法
M = int(input())
N = int(input())
K = int(input())
row_odd = [False] * (M + 1)
col_odd = [False] * (N + 1)
for _ in range(K):
brush_type, num = input().split()
idx = int(num)
if brush_type == "R":
row_odd[idx] = not row_odd[idx]
else:
col_odd[idx] = not col_odd[idx]
R = sum(row_odd)
C = sum(col_odd)
gold = R * (N - C) + (M - R) * C
print(gold)
也可以用 set 来写。set 里只保留被刷了奇数次的行和列:
M = int(input())
N = int(input())
K = int(input())
rows = set()
cols = set()
for _ in range(K):
brush_type, num = input().split()
idx = int(num)
if brush_type == "R":
if idx in rows:
rows.remove(idx)
else:
rows.add(idx)
else:
if idx in cols:
cols.remove(idx)
else:
cols.add(idx)
R = len(rows)
C = len(cols)
print(R * (N - C) + (M - R) * C)
C++ 解法
#include <iostream>
#include <vector>
using namespace std;
int main() {
int M, N, K;
cin >> M >> N >> K;
vector<bool> rowOdd(M + 1, false);
vector<bool> colOdd(N + 1, false);
for (int i = 0; i < K; i++) {
char brushType;
int idx;
cin >> brushType >> idx;
if (brushType == 'R') {
rowOdd[idx] = !rowOdd[idx];
} else {
colOdd[idx] = !colOdd[idx];
}
}
long long R = 0;
long long C = 0;
for (int i = 1; i <= M; i++) {
if (rowOdd[i]) R++;
}
for (int j = 1; j <= N; j++) {
if (colOdd[j]) C++;
}
long long gold = R * (N - C) + (M - R) * C;
cout << gold << "\n";
return 0;
}
复杂度分析
我们没有建立 M x N 的 grid。
处理 K 次操作需要:
O(K)
统计 odd rows 和 odd columns 需要:
O(M + N)
所以总时间复杂度是:
O(M + N + K)
空间复杂度是:
O(M + N)
如果用 set,只存 odd 的行和列,空间可以看作 O(number of odd rows + number of odd columns)。
常见错误
- 建立整张
M x N画布,导致内存或时间不够。 - 统计刷了多少次,但最后忘记只看奇偶性。
- 把同一行刷两次仍然当成 gold 贡献,其实两次 toggle 会抵消。
- 公式写反,忘记 gold 是“行和列正好一个 odd”。
- C++ 用
int存答案,可能在大数据下溢出。
复习问题
- 为什么一个格子的最终颜色只和 toggle 次数的奇偶性有关?
- 为什么同一行刷两次等于没有刷?
R * (N - C)表示哪一类 gold cell?(M - R) * C表示哪一类 gold cell?- 为什么这题不需要真的建立整个 grid?
Questions or feedback?
Have a question about this article or want to suggest an improvement? Send us a private message.
有问题或建议?
如果你对本文有疑问,或希望提出改进建议,请给我们发送私密留言。
相关推荐
继续探索相关学习路径。
根据当前页面内容,下面这些页面可以帮助学生和家长更快找到合适的下一步。