CF1864D 题解

发布时间 2023-09-01 10:31:48作者: yuhang-ren

CF1864D Matrix Cascade 题解

洛谷

Codeforces

Description

给定一个 \(n\times n\) 的 01 矩阵。

定义一次操作为:选择矩阵上第 \(i\) 行第 \(j\) 列的格子 \((i,j)\),将其取反,并取反所有满足 \(x > i, x - i \ge |y - j|\) 的位置 \((x,y)\)

其中,“取反”的意思为:把 \(0\) 变为 \(1\)\(1\) 变为 \(0\)

求要把给定矩阵全变为 \(0\) 的最少操作次数。

\(t\) 组测试数据。

数据范围:\(1\le t\le 10^5,2\le n\le 3\times10^3\),保证所有测试数据中 \(1\le \sum n^2\le 9\times10^6\)

Solution

观察题目的操作,我们可以很容易的发现每次操作改变的是下面的一个等腰直角三角形(当然会因为矩阵有边界被截掉一部分)。

观察这个区域左边的那条线,发现它的表达式为 \(x + y = i + j\)\(\left(i,j \right)\) 是选择的位置。也就是说,我们选择一个位置 \((i,j)\),一定不能改变 \(x + y < i + j\) 的点 \((x,y)\),于是我们可以按照 \(x + y\) 从小到大的顺序枚举所有点,在 \(x + y\) 相同时,显然我们选择更高(即 \(x\) 更小)的位置能影响的范围更大,这样就能确定唯一也是最优的方案。

如果我们对于每个点直接暴力修改得到的是 \(O(n^4)\) 的算法。

按照顺序枚举后,我们就不需要考虑左边界了,让我们来看一下右侧的边界,根据题目条件很容易得到满足的条件是是 \(x - y > i - j\)。因此我们可以使用树状数组实现差分,维护每一个 \(x - y\) 被更新了几次,枚举到一个点时如果 \(\left([mp_{i,j} = \texttt{1}] + \texttt{这个点被更新的次数}\right) \equiv 1 \pmod 2\),我们需要更新 \(i - j\) 并让 \(ans \gets ans + 1\)

需要注意的是,树状数组无法支持负下标,因此我们将所有的 \(x - y\) 统一变为 \(x - y + n\),枚举范围变为 \([0,2n]\) 即可。

Codes

#include <bits/stdc++.h>
using namespace std;
#define int long long
void read(int &p)
{
    p = 0;
    int k = 1;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
        {
            k = -1;
        }
        c = getchar();
    }
    while (c >= '0' && c <= '9')
    {
        p = p * 10 + c - '0';
        c = getchar();
    }
    p *= k;
    return;
}
void write_(int x)
{
    if (x < 0)
    {
        putchar('-');
        x = -x;
    }
    if (x > 9)
    {
        write_(x / 10);
    }
    putchar(x % 10 + '0');
}
void writesp(int x)
{
    write_(x);
    putchar(' ');
}
void writeln(int x)
{
    write_(x);
    putchar('\n');
}
int T;
int n;
char mp[5100][5100];
int tree[10011];
inline int lowbit(const int &i)
{
    return (-i)&(i);
}
inline void update(int x)
{
    for(;x <= 2 * n;x+= lowbit(x))
    {
        tree[x]++;
    }
}
inline int query(int x)
{
    int res = 0;
    for(;x;x -= lowbit(x))
    {
        res += tree[x];
    }
    return res;
}
int ans;
void solution()
{
    ans = 0;
    read(n);
    for(int i = 1;i <= 2 * n;i++)
    {
        tree[i] = 0;
    }
    for(int i = 1;i <= n;i++)
    {
        scanf("%s",mp[i] + 1);
    }
    for(int sum = 2;sum <= 2 * n;++sum)
    {
        for(int i = max(1LL,sum - n),j = min(n,sum - 1);i < sum && i <= n;i++,j--)
        {
            if(((mp[i][j] - '0') + query(i - j + n)) & 1)
            {
                ++ans;
                update(i - j + n);
            }
        }
    }
    writeln(ans);
}
signed main()
{
    read(T);
    while(T--)
    {
        solution();
    }
    return 0;
}