D. Matrix Cascade
D. Matrix Cascade
There is a matrix of size which consists of 0s and 1s. The rows are numbered from to from top to bottom, the columns are numbered from to from left to right. The cell at the intersection of the -th row and the -th column is denoted as .
AquaMoon wants to turn all elements of the matrix to 0s. In one step she can perform the following operation:
- Select an arbitrary cell, let it be , then invert the element in and also invert all elements in cells for and . To invert a value means to change it to the opposite: 0 changes to 1, 1 changes to 0.
Help AquaMoon determine the minimum number of steps she need to perform to turn all elements of the matrix to 0s. We can show that an answer always exists.
Input
Each test contains multiple test cases. The first line contains the number of test cases (). The description of the test cases follows.
The first line of each test case contains an integer ().
The -th of the following lines contains a binary string only of characters 0 and 1, of length .
It is guaranteed that the sum of over all test cases does not exceed .
Output
For each test case, print the minimum number of steps.
Example
input
3 5 00100 01110 11111 11111 11111 3 100 110 110 6 010101 111101 011110 000000 111010 001110
output
1 2 15
Note
In the first test case, we can use the following scheme:
- perform the operation on the cell .
Clearly, the elements of the initial matrix are not all 0, so at least one operation is required. Thus, is the answer.
In the second test case, we use the following scheme:
- perform the operation on the cell ;
- perform the operation on the cell .
It can be shown that there is no way to convert all elements to 0s in or steps, so the answer is exactly .
解题思路
先说一下我一开始的做法。
首先对于矩阵的第行,如果某个格子是那么我们必然至少要对这个格子进行次操作,否则由于操作只会影响往后行的格子那么这个格子不会得到改变。处理完第行后再考虑第行,此时第行已经全为了,如果要对第行操作那么必然是偶数次操作,而对同一个格子进行偶数次操作相当于没有操作,因此我们不能再对之前的行进行操作。对于第行,如果这个格子本来是并且之前行对这个格子影响了偶数次,或者这个位置本来是并且之前行对这个格子影响了奇数次,那么我们就要对这个格子进行一次操作。同理之后的行也是也是这样处理。
因此我们从上到下遍历每一个格子,如果某个格子本来是并且之前行对这个格子影响了偶数次,或者这个位置本来是并且之前行对这个格子影响了奇数次,那么就对这个格子进行一次操作,最终答案就是进行操作的次数。
现在的问题就是,如果对某个格子操作后,如何快速对满足的格子累加一次翻转的次数,可以发现这个格子都是夹在斜率为和的直线之间的格子,如下图,其中斜率为和的直线用红色标出来:
假设原点在左上角,那么每个格子都可以对应一条斜率为和的直线。斜率为的直线可以表示为,那么我们可以用截距来表示格子属于斜率为截距为的直线。同理用来表示格子属于斜率为截距为的直线。为了方便,这里对斜率为的直线的截距都加上偏移量,使得所有截距都映射到,对斜率为的直线的截距都加上偏移量,使得所有截距也映射到。
对格子操作后,我们先对斜率为,截距在所对应直线的所有格子都加上,如下图:
再对斜率为,截距在所对应直线的所有格子都加上,如下图:
这样就可以对所能影响到的格子都累加一次翻转的次数。可以发现这样做的话在上面的某些格子会被,不过这个没关系,因为上面行的格子我们不会再进行操作了,只要保证同行以及下面行的格子没受到影响就行。
由于涉及到区间动态累加的操作,因此我们可以用树状数组来维护一个差分数组,这样就可以实现区间修改,单点查询。另外由于我们只关心某个格子被影响了奇数次还是偶数次,并且每次操作都是和,因此可以把所有的运算转换成异或,相当于模加法。
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 3010; 5 6 int n; 7 char g[N][N]; 8 int tr1[N * 2], tr2[N * 2]; 9 10 int lowbit(int x) { 11 return x & -x; 12 } 13 14 void add(int *tr, int x) { 15 for (int i = x; i <= n << 1; i += lowbit(i)) { 16 tr[i] ^= 1; 17 } 18 } 19 20 int query(int *tr, int x) { 21 int ret = 0; 22 for (int i = x; i; i -= lowbit(i)) { 23 ret ^= tr[i]; 24 } 25 return ret; 26 } 27 28 void solve() { 29 scanf("%d", &n); 30 for (int i = 0; i < n; i++) { 31 scanf("%s", g[i]); 32 } 33 memset(tr1, 0, n * 2 + 10 << 2); 34 memset(tr2, 0, n * 2 + 10 << 2); 35 int ret = 0; 36 for (int i = 0; i < n; i++) { 37 for (int j = 0; j < n; j++) { 38 int k1 = j - i + n, k2 = j + i + 1; 39 if (g[i][j] + query(tr1, k1) + query(tr2, k2) & 1) { 40 ret++; 41 add(tr1, 1), add(tr1, k1 + 1); 42 add(tr2, 1), add(tr2, k2); 43 } 44 } 45 } 46 printf("%d\n", ret); 47 } 48 49 int main() { 50 int t; 51 scanf("%d", &t); 52 while (t--) { 53 solve(); 54 } 55 56 return 0; 57 }
在参考别人的做法后发现可以优化到。
我们反过来考虑每个格子会被哪些格子影响到,很明显就是满足的格子,如下图:
然后我们维护三个数组,表示在斜率为且经过的直线上(即上图蓝色的部分),从直线最左上角的格子到翻转的次数的累加,因此有。表示在斜率为且经过的直线上(即绿色的部分),从直线最右上角的格子到翻转的次数的累加,因此有。表示所有能影响到格子(即红色部分)的翻转的次数的累加,因此有。这样就知道了格子被影响到的次数了。
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 3010; 5 6 char g[N][N]; 7 int a[N][N], b[N][N], c[N][N]; 8 9 void solve() { 10 int n; 11 scanf("%d", &n); 12 for (int i = 1; i <= n; i++) { 13 scanf("%s", g[i] + 1); 14 } 15 int ret = 0; 16 for (int i = 1; i <= n; i++) { 17 for (int j = 1; j <= n; j++) { 18 a[i][j] = a[i - 1][j - 1]; 19 b[i][j] = b[i - 1][j + 1]; 20 c[i][j] = c[i - 1][j] ^ a[i - 1][j - 1] ^ b[i - 1][j + 1]; 21 if (c[i][j] ^ g[i][j] - '0') { 22 ret++; 23 a[i][j] ^= 1; 24 b[i][j] ^= 1; 25 c[i][j] ^= 1; 26 } 27 } 28 } 29 printf("%d\n", ret); 30 } 31 32 int main() { 33 int t; 34 scanf("%d", &t); 35 while (t--) { 36 solve(); 37 } 38 39 return 0; 40 }
参考资料
Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2) Editorial:https://codeforces.com/blog/entry/119772
Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2):https://www.cnblogs.com/cjjsb/p/17662353.html
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17666687.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效