CFR-857解题报告
A. The Very Beautiful Blanket
题意:构造一个 \(n\times m\) 的矩阵,使得任意 \(4\times 4\) 的子矩阵中,左上 \(2\times 2\) 与右下 \(2\times 2\) 的矩阵的异或和,等于右上 \(2\times 2\) 与左下 \(2\times 2\) 的矩阵的异或和。
这个限制看起来非常难以处理,但非常宽松,于是可以想到人为构造一种更强的限制,使得新限制是原限制的充分条件,且较容易构造。
一种容易想到的方式为,只要让任意 \(2\times 2\) 的子矩阵的异或和均为 \(0\),显然可满足要求。以下为两种较简单的构造:
做法一
因为任意 \(2\times 2\) 的子矩阵异或和为 \(0\),则只要有三个确定了,第四个唯一确定。于是可以发现,第一行和第一列确定后,就能唯一确定一个 \(n\times m\) 的矩阵(递推即可)。第一行和第一列可以随机,正确率足够通过本题。如果放不下心可以最后加一个判断,不合法则重新随机。
By cxm1024
long long a[210][210];
void Solve(int test) {
int n, m;
cin >> n >> m;
while (1) {
for (int i = 1; i <= n; i++)
a[i][1] = abs((long long)(1ull * rand() * rand() * rand()));
for (int i = 1; i <= m; i++)
a[1][i] = abs((long long)(1ull * rand() * rand() * rand()));
for (int i = 2; i <= n; i++)
for (int j = 2; j <= m; j++)
a[i][j] = a[i - 1][j - 1] ^ a[i - 1][j] ^ a[i][j - 1];
// set<int> s;
// for (int i = 1; i <= n; i++)
// for (int j = 1; j <= m; j++)
// s.insert(a[i][j]);
// if (s.size() != n * m) continue;
// 注释部分为判断合法性,不加也可通过
cout << n * m << endl;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
cout << a[i][j] << " ";
cout << endl;
}
break;
}
}
做法二
第二种为直接构造,思维难度相对较高。对于 \(2\times 2\) 的子矩阵中的元素,考虑在二进制位上分段处理,分为前后两段,前面的段每一行内部相同,行之间不同;后面的段每一列内部相同,列之间不同。这样,同行的两个元素异或会把前面的段抵消,同列的两个元素异或会把后面的段抵消,保证异或和为 \(0\)。
实现上,直接令 \(a_{i,j}=i\times 2^k+j\),其中 \(k\) 是足以区分两段的常数(本题中取 \(8\) 以上即可)。
By jiangly
void solve() {
int n, m;
std::cin >> n >> m;
std::cout << n * m << "\n";
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
std::cout << (i << 10) + j << " \n"[j == m - 1];
}
}
}
做法三
做法二有些过于技巧性,以至于我们的注意力容易被巧妙的二进制操作所吸引。实际上,此做法关键的妙处并不在此。
首先来看另一种随机的做法:
By flowerletter
#include <bits/stdc++.h>
using namespace std;
mt19937_64 hua(time(0));
using u64 = unsigned long long;
int main() {
// freopen("in.txt", "r", stdin);
ios::sync_with_stdio(false), cin.tie(0);
int T;
for(cin >> T; T; T --) {
int n, m;
cin >> n >> m;
cout << n * m << '\n';
vector<u64> a(n), b(m);
for(int i = 0; i < n; i ++) a[i] = hua() % LLONG_MAX;
for(int i = 0; i < m; i ++) b[i] = hua() % LLONG_MAX;
for(int i = 0; i < n; i ++) {
for(int j = 0; j < m; j ++) {
cout << (a[i] ^ b[j]) << ' ';
}
cout << '\n';
}
}
return 0;
}
为什么这是对的?考察一个 \(2\times 2\) 的子矩阵 \(\begin{bmatrix}(x,y)&(x,y+1)\\(x+1,y)&(x+1,y+1)\end{bmatrix}\),其异或和为 \((a_x\oplus b_y)\oplus(a_x\oplus b_{y+1})\oplus(a_{x+1}\oplus b_y)\oplus(a_{x+1},b_{y+1})=0\)。这是因为上下两个的 \(b\) 相同,左右两个的 \(a\) 相同,内部抵消完毕。而 \(a,b\) 的随机性又保证了不会重复。
现在回过头来看做法二,就会发现,其实是钦定本做法的 \(a_x=x\times 2^k,b_y=y\),以二进制的操作来保证每行没列不重复。
总结
这里的做法二和做法三都巧妙且简洁,是我目前看到的 best code。
本题的精髓思考在于以下两点:
- 想到将原限制转化为任意 \(2\times 2\) 的格子异或和为 \(0\)
- 用左右能够抵消的量和上下能够抵消的量组合,来填每个格子,在此基础上使用随机/二进制来保证不重复。