CCC 2021 J5/S2 Modern Art Solution: Count Gold Cells with Parity



Start NorthStar

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。

Share this article

Copy the link, open your phone share sheet, scan the QR code for WeChat, or send by email.




Email

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 row x
  • C y: toggle column y

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:

  1. odd row + even column
  2. 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

  1. Read M, N, and K.
  2. Create row_odd and col_odd.
  3. For each operation:
  • toggle the row parity for R x
  • toggle the column parity for C y
  1. Count how many rows are odd.
  2. Count how many columns are odd.
  3. 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

  1. Building and updating the full M x N canvas.
  2. Tracking full paint counts when only parity matters.
  3. Forgetting that toggling the same row twice cancels out.
  4. Counting cells where both row and column are odd as gold, even though two toggles return them to black.
  5. Using int for the final answer in C++, which may overflow.

Review Questions

  1. Why does only odd/even toggle count matter?
  2. Why does painting the same row twice cancel out?
  3. What does R * (N - C) count?
  4. What does (M - R) * C count?
  5. 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)]

然后每次操作都真的把一整行或一整列翻转。

这个思路在小数据上可以工作,但在竞赛题里通常不够好。因为 MN 可能很大,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:

  1. 行是 odd,列是 even
  2. 行是 even,列是 odd

所以 gold cell 的数量是:

R * (N - C) + (M - R) * C

第一部分 R * (N - C) 表示:选一条 odd row,再选一条 even column。

第二部分 (M - R) * C 表示:选一条 even row,再选一条 odd column。

算法步骤

  1. 读入 M, N, K
  2. 建立 row_oddcol_odd
  3. 对每次操作:
  • 如果是 R x,反转 row_odd[x]
  • 如果是 C y,反转 col_odd[y]
  1. 统计 odd rows 的数量 R
  2. 统计 odd columns 的数量 C
  3. 用公式计算答案:
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)

常见错误

  1. 建立整张 M x N 画布,导致内存或时间不够。
  2. 统计刷了多少次,但最后忘记只看奇偶性。
  3. 把同一行刷两次仍然当成 gold 贡献,其实两次 toggle 会抵消。
  4. 公式写反,忘记 gold 是“行和列正好一个 odd”。
  5. C++ 用 int 存答案,可能在大数据下溢出。

复习问题

  1. 为什么一个格子的最终颜色只和 toggle 次数的奇偶性有关?
  2. 为什么同一行刷两次等于没有刷?
  3. R * (N - C) 表示哪一类 gold cell?
  4. (M - R) * C 表示哪一类 gold cell?
  5. 为什么这题不需要真的建立整个 grid?

Questions or feedback?

Have a question about this article or want to suggest an improvement? Send us a private message.

Send Feedback

有问题或建议?

如果你对本文有疑问,或希望提出改进建议,请给我们发送私密留言。

发送反馈

Back to CP Weekly Challenge
返回 CP Weekly Challenge

滚动至顶部