CF1205C Palindromic Paths 题解
很好的一道题,思路自然,步骤清晰,结论好猜。唯一的缺点可能只是我赛时没有看到。
构造可行解
看到题目,也许我们很快就能想出一个做法:每次询问 \((i,j,i+1,j)\),每行第一个额外询问 \((i,j,i+1,j)\),这样询问总共 \(n ^ 2 - 1\) 次即可。
当我们怀疑看错题目重新检查时发现了被微软翻译漏掉的条件:\(x_1+y_1+2 \le x_2 + y_2\)。
长度为 \(2\) 不行,我们可以长度为 \(3\)。每次询问 \((i,j,i,j + 2)\),每行第一个额外询问 \((i,j,i + 2,j)\),第一行第一个额外询问 \((1,1,2,2)\),偶数行的 \(i\) 从 \(2\) 开始。
由于我们已知点 \((1,1)\) 为 \(1\),这样相当于往棋盘上染成国际象棋的黑白两色。不妨设点 \((1,1)\) 为黑色,那么我们知道黑色的棋子的确切值。
a[1][1] = 1;
a[n][n] = 0;
for (int x = 1; x <= n; ++x) {
int p = (x & 1 == 1 ? 1 : 2);
if (x <= n - 2) {
cout << "? " << x << ' ' << p << ' ' << x + 2 << ' ' << p << endl;
a[x + 2][p] = a[x][p] ^ (!read());
}
for (int y = p; y <= n - 2; y += 2) {
if (y == n - 2 && x == n) break;// 最后一个格子已知,没必要求
cout << "? " << x << ' ' << y << ' ' << x << ' ' << y + 2 << endl;
a[x][y + 2] = a[x][y] ^ (!read());
}
if (x == 1 && x < n) {
cout << "? " << x << ' ' << p << ' ' << x + 1 << ' ' << p + 1 << endl;
a[x + 1][p + 1] = a[x][p] ^ (!read());
}
}
我们不知道白色格子的确切值,但是我们可以知道白色格子的相对值。也就是我们假设点 \((1,2)\) 为 \(1\),那么我们可以求出所有白色格子的值。方法与上类似,这里不再赘述。
b[1][2] = 1;
for (int x = 1; x <= n; x += 2) {
for (int y = 2; y <= n - 1; y += 2) {
if (y < n - 1) {
cout << "? " << x << ' ' << y << ' ' << x << ' ' << y + 2 << endl;
++cnt;
b[x][y + 2] = b[x][y] ^ (!read());
}
if (x < n) {
cout << "? " << x << ' ' << y << ' ' << x + 1 << ' ' << y + 1 << endl;
b[x + 1][y + 1] = b[x][y] ^ (!read());
}
}
if (x < n) {
cout << "? " << x << ' ' << 2 << ' ' << x + 2 << ' ' << 2 << endl;
b[x + 2][2] = b[x][2] ^ (!read());
cout << "? " << x + 1 << ' ' << 1 << ' ' << x + 2 << ' ' << 2 << endl;
b[x + 1][1] = b[x + 2][2] ^ (!read());
}
}
现在唯一的问题是,点 \((1,2)\) 既有可能为 \(1\),也有可能为 \(0\)。我们需要判断两种方案哪一种是正确的。在之前的过程中我们对除了 \((1,1),(1,2),(n,n)\) 三个点之外的所有点进行了一次判断,也就是说我们还剩下 \(3\) 次询问。
判断正确性
既然所有长度为 \(3\) 的路径都被我们考虑过了,我们顺理成章地考虑长度为 \(4\) 的路线。需要注意的是,我们的解法不能与 \((i,j,i,j + 3)\) 这样的询问有关,因为当 \(n = 3\) 时这是一个非法询问(为什么更长长度的路线不考虑?也是思考 \(n=3\) 的情况可以把它们全部排除)。
也就是说,我们应该的询问类似于 \((i,j,i + 2,j + 1)\)。由于这样的询问在给出否定答案时只能揭露点值的关系,无法给出具体信息,所以我们的判定方法只能是一种方案这个询问为真,另一种方案这个询问为假。在 \((n - 2) \times (n - 1)\) 个矩阵中找出一个满足条件的矩阵进行验证即可。
有没有可能这样的矩阵不存在?我们来分类讨论一下。
两种方案均为真
这种情况不存在。因为 \((i,j)\) 必定不和 \((i + 2,j + 1)\) 同色(对颜色的定义参照构造可行解一节),所以两种方案中有且仅有一个点发生改变,所以必定在一种情况里 \((i,j) = (i + 2,j + 1)\),在另一种情况中 \((i,j) \neq (i + 2, j + 1)\)。
两种情况均为假
一种情况为假有两种可能:
- 两端点不同。
- 路径中间上的两个点不同。
所有路径中央的两个点必然属于异色,所以在一种情况不同时,另一种情况必然相同。因此两种方案必定分别属于上面的两种可能。所以这种情况只可能是下面的形式:
第一种:
11 10
10 00
01 00
第二种:
10 11
01 11
11 10
由于我们希望所有 \((i,j,i + 2,j + 1)\) 的询问答案均为假,所以我们必须把矩阵补全。但是我们发现了两点:
- 这两种情况互斥,出现了第一种情况就不可能出现第二种情况。
如果 \(n=3\),有一个看似成功的补全:
110
101
011
但这样 \((n,n) = 1\),不符合题目条件。
否则如果两个连续的 \(1\) 不出现在最后,就必定会有一个矩形不存在于两种情况之间,不符合我们的条件。
- 单一的情况不能补全矩形。
原因是我们不可能出现多个连续的 \(1\) 的同时还保证每个 \(1\) 下面的数都为一样的数。
因此,这种情况不可能存在。
于是我们找到一个两种方案矛盾的矩形进行询问,选择符合条件的那边即可。总共用了 \(n^2 - 2\) 次询问,超额完成任务。
代码实现
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 55;s
int read() {
int x;cin >> x;return x;
}
struct mar {
int a[4][3];
bool check() {
if (a[1][1] != a[3][2]) return false;
if (a[1][2] == a[2][2]) return true;
if (a[2][1] == a[2][2]) return true;
if (a[2][1] == a[3][1]) return true;
return false;
}
};
int n;
int a[N][N], b[N][N];
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int x = 1; x <= n; ++x) {
for (int y = 1; y <= n; ++y) {
a[x][y] = -1;
}
}
a[1][1] = 1;
a[n][n] = 0;
for (int x = 1; x <= n; ++x) {
int p = (x & 1 == 1 ? 1 : 2);
if (x <= n - 2) {
cout << "? " << x << ' ' << p << ' ' << x + 2 << ' ' << p << endl;
a[x + 2][p] = a[x][p] ^ (!read());
}
for (int y = p; y <= n - 2; y += 2) {
if (y == n - 2 && x == n) break;
cout << "? " << x << ' ' << y << ' ' << x << ' ' << y + 2 << endl;
a[x][y + 2] = a[x][y] ^ (!read());
}
if (x == 1 && x < n) {
cout << "? " << x << ' ' << p << ' ' << x + 1 << ' ' << p + 1 << endl;
a[x + 1][p + 1] = a[x][p] ^ (!read());
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
b[i][j] = -1;
}
}
b[1][2] = 1;
for (int x = 1; x <= n; x += 2) {
for (int y = 2; y <= n - 1; y += 2) {
if (y < n - 1) {
cout << "? " << x << ' ' << y << ' ' << x << ' ' << y + 2 << endl;
b[x][y + 2] = b[x][y] ^ (!read());
}
if (x < n) {
cout << "? " << x << ' ' << y << ' ' << x + 1 << ' ' << y + 1 << endl;
b[x + 1][y + 1] = b[x][y] ^ (!read());
}
}
if (x < n) {
cout << "? " << x << ' ' << 2 << ' ' << x + 2 << ' ' << 2 << endl;
b[x + 2][2] = b[x][2] ^ (!read());
cout << "? " << x + 1 << ' ' << 1 << ' ' << x + 2 << ' ' << 2 << endl;
b[x + 1][1] = b[x + 2][2] ^ (!read());
}
}
// we find a and b
mar z, f;
int chk = 0, pd = 0;
for (int i = 1; i <= n - 2; ++i) {
for (int j = 1; j <= n - 1; ++j) {
for (int x = i, wx = 1; x <= i + 2; ++x, ++wx) {
for (int y = j, wy = 1; y <= j + 1; ++y, ++wy) {
if (a[x][y] == -1) z.a[wx][wy] = b[x][y];
else z.a[wx][wy] = a[x][y];
}
}
for (int x = i, wx = 1; x <= i + 2; ++x, ++wx) {
for (int y = j, wy = 1; y <= j + 1; ++y, ++wy) {
if (a[x][y] == -1) f.a[wx][wy] = !b[x][y];
else f.a[wx][wy] = a[x][y];
}
}
if (z.check() != f.check()) {
cout << "? " << i << ' ' << j << ' ' << i + 2 << ' ' << j + 1 << endl;
int tmp = read();
if (tmp == f.check()) chk = 1;
pd = 1;
break;
}
}
if (pd == 1) break;
}
cout << "!" << endl;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (a[i][j] != -1) cout << a[i][j];
else cout << (b[i][j] ^ chk);
}
cout << endl;
}
return 0;
}