CF1205C Palindromic Paths 题解

发布时间 2023-06-16 21:17:15作者: _maze

很好的一道题,思路自然,步骤清晰,结论好猜。唯一的缺点可能只是我赛时没有看到。


构造可行解

看到题目,也许我们很快就能想出一个做法:每次询问 \((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)\)

两种情况均为假

一种情况为假有两种可能:

  1. 两端点不同。
  2. 路径中间上的两个点不同。

所有路径中央的两个点必然属于异色,所以在一种情况不同时,另一种情况必然相同。因此两种方案必定分别属于上面的两种可能。所以这种情况只可能是下面的形式:

第一种:

11 10
10 00
01 00

第二种:

10 11
01 11
11 10

由于我们希望所有 \((i,j,i + 2,j + 1)\) 的询问答案均为假,所以我们必须把矩阵补全。但是我们发现了两点:

  1. 这两种情况互斥,出现了第一种情况就不可能出现第二种情况。

如果 \(n=3\),有一个看似成功的补全:

110
101
011

但这样 \((n,n) = 1\),不符合题目条件。

否则如果两个连续的 \(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;	
}