题解:P10635 BZOJ3517 翻硬币
wangbinfeng · · 题解
- 原题数据过水,建议通过本题后可以用我出的加强版测试代码强度。具体的,将
n 的范围扩大到了1\times 10^4 。
理解 1(数学做法):
感觉目前已有题解写的都难以看懂,所以写一篇自认为比较好理解的,同时与目前题解的做法均不大相同。
下文中,
A_{i,j} 表示在(i,j) 处原本的状态,C_{i,j} 表示是否在(i,j) 处执行整行整列的修改,A_{i,j}\in\{0,1\},C_{i,j}\in\{0,1\} 。
考虑到一个位置如果被翻转两次那么结果会不变,很容易想到异或。更进一步思考:如果想将
那么又可以得出:
也就是说,我们可以通过输入的
那么如果要将所有的数均变为
如果直接这么做时间复杂度为
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000 + 9;
int n, ans, a[maxn][maxn], line[maxn], column[maxn];
string s;
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> s;
for (int j = 1; j <= n; j++)
a[i][j] = s[j - 1] - '0', line[i] ^= a[i][j], column[j] ^= a[i][j];
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
ans += line[i] ^ column[j] ^ a[i][j];
cout << min(ans, n * n - ans);
}
理解 2:
考虑到对于每一个点,想当且仅当修改这一个点
具体地讲:对于整个矩阵的点
Q 分为如下三种情况:
- 不在十字上:那么这个点一定会他水平方向和竖直方向的点修改两次,它的值并不会发生变化。
- 在十字上但不是
P 点:那么一定会被同一行或同一列的所有点修改n 次,由于n 是偶数,所以它的值也不会发生变化。
如果两个点都要修改,这两个点一定会造成最少两个交点,那么这些交点显然不需要修改(即异或两次)。用一个数组统计每个点是否修改即可。
用
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000 + 9;
int n, ans, a[maxn][maxn], f[maxn][maxn];
string s;
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> s;
for (int j = 1; j <= n; j++)
a[i][j] = s[j - 1] - '0';
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (a[i][j])
{
for (int k = 1; k <= n; k++)
f[i][k] ^= 1, f[k][j] ^= 1;
f[i][j] ^= 1;
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
ans += f[i][j];
cout << min(ans, n * n - ans);
}
但是还是可以考虑优化,注意到这个算法的复杂度瓶颈在于枚举 f 数组需要修改的位置,那么能否省略掉这个循环呢?答案是可以的,因为对于任意的
代码暂时省略,交给读者自行完成。
公式又多又长,写了好久,我要亖了,如果有错烦请请评论区交流。
- 鸣谢:
@wangbinfeng 耗时 3h 完成本题解的创作。- @NATO_Fan_Club 首先质疑本题已有题解并启发本人想到第二种理解,具体见这个帖子。
- @a1co0av5ce5az1cz0ap_ 的题解,让本人借鉴了部分的证明(即修改十字的准确证明,本人之前写法过于主观难以理解)。
难以理解的现有题解(所有在本题解完成前的 luogu 题解)激励本人完成本题解的创作。