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。

本题的精髓思考在于以下两点:

  1. 想到将原限制转化为任意 \(2\times 2\) 的格子异或和为 \(0\)
  2. 用左右能够抵消的量和上下能够抵消的量组合,来填每个格子,在此基础上使用随机/二进制来保证不重复。
posted @ 2023-03-10 23:35  曹轩鸣  阅读(14)  评论(0编辑  收藏  举报