中文题意

第一行给出一个整数

接下来给出一个的矩阵,你有种颜料,每次你可以选择一个矩阵中的格点填涂一个小正方形,你选择的格点将会成为这个正方形的左上点,并且每个格点做为左上方点只能选择一次,下一次填涂颜料会把上次的颜色覆盖,初始化矩阵都是没涂过颜色的你可以当作涂了颜色,你需要选择中的全部点,填涂次,问最终状态是给出的矩阵的第一次填涂的颜色有多少种?

Solution

正向思考我们很难处理,显然对于当前状态,之前可填涂的方案有很多很多种,难以固定。

但是我们反向思考的话,我们要求出第一次填涂的颜色可以是那些,你们我找到什么颜色不能第一次填涂,最后把减掉这些颜色,是不是也是同样找到了答案呢。

那么如何寻找不能第一次填涂的颜色,我们找到一种颜色它覆盖的矩阵范围,我们把这个矩阵中填涂次数加,那么对于填涂次数的格点,说明最终矩阵中留下的点,一定不能成为第一次填涂的点,因为你会把它覆盖最起码一次,如果第一次就选择了这个点,后续无法找到覆盖方法保留这个点。

还有一种特例,如果整个矩阵中只有种颜色,你又要填涂次,说明最终颜色一定是最后填涂,使用寻找到的方案数应该是,如果矩阵,直接输出种颜色即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 7;
#define DEBUG(x) cout << #x << ":" << x << '\n'
int a[N][N], n;
int up[N * N], r[N * N], dw[N * N], l[N * N];
int sum[N][N];

int main() {
    scanf("%d", &n);
    memset(up, 0x3f, sizeof up);
    memset(l, 0x3f, sizeof l);
    if (n == 1) { return puts("1"), 0; }
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            scanf("%d", &a[i][j]);
            int x = a[i][j];
            up[x] = min(up[x], i);
            r[x] = max(r[x], j);
            dw[x] = max(dw[x], i);
            l[x] = min(l[x], j);
        }
    }
    int cnt = 0; // 先记录出现了几种颜色
    for (int i = 1; i <= n * n; ++i) {
        if (!r[i])    continue;
        ++cnt;
        ++sum[up[i]][l[i]];
        --sum[up[i]][r[i] + 1];
        --sum[dw[i] + 1][l[i]];
        ++sum[dw[i] + 1][r[i] + 1];
    }
    if (cnt == 1) { return printf("%d\n", n * n - 1), 0; }
    // 二维差分
    unordered_set<int> st;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j) {
            sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
            if (sum[i][j] >= 2)    st.insert(a[i][j]);
        }
    printf("%d\n", n * n - st.size());
    return 0;
}