# CF 538 D. Weird Chess
:::info
[Link](https://codeforces.com/contest/538/problem/D)
:::
:::spoiler 目錄
[toc]
:::
## 題意
給定一個$n\times n$的棋盤,盤面上有至少一枚以上以小寫"$o$"表示的棋子和數個棋子可以移動過去的位置以小寫"$x$"代表,其他位置則以"$.$"表示。
請找出一個$(2n - 1) * (2n - 1)$的棋盤,一樣以$x$表示可移動位置,盤上只有唯一一枚在中心的棋子 $o$ ,棋盤上面所有的 $x$ 可以剛好符合前面給的棋盤的移動範圍。
同一棋盤可能會有多種對應的解,只要輸出其中一個即可
如假設給定以下棋盤:
```
oxxxx
x...x
x...x
x...x
xxxxo
```
則下面的棋盤為其中一種解:
```
....x....
....x....
....x....
....x....
xxxxoxxxx
....x....
....x....
....x....
....x....
```
## 官解
### 思路
思路基本上和初見解一樣,就不贅述了。
>> 不過題解說有$O{(n^2\log{n})}$的做法,不過它沒有提供實作方式,但底下有留言說可以用一維FFT做,但請原諒我太弱,沒辦法自己做出來QQ
### code
>> 仿照官解提供的作法寫的,老實說沒有比初見解好到哪裡去XD
```cpp=
#pragma GCC optimize("O3,unroll-loops")
#pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt")
#include <bits/stdc++.h>
using namespace std;
string board[55];
int ans[105][105];
void solve() {
int n;
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> board[i];
}
const int len = 2 * n - 1, mid = n - 1;
for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) {
if (board[i][j] != 'o') continue;
for (int x = 0; x < n; ++x) for (int y = 0; y < n; ++y) {
if (board[x][y] != '.') continue;
ans[x - i + mid][y - j + mid] = 1;
}
}
for (int x = 0; x < n; ++x) for (int y = 0; y < n; ++y) {
if (board[x][y] != 'x') continue;
bool failed = true;
for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) {
if (board[i][j] != 'o') continue;
failed &= ans[x - i + mid][y - j + mid];
}
if (failed) {
cout << "NO\n";
return;
}
}
ans[mid][mid] = 2;
cout << "YES\n";
for (int i = 0; i < len; ++i) {
for (int j = 0; j < len; ++j) {
cout << "x.o"[ans[i][j]];
}
cout << "\n";
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
solve();
}
```
## 初見解
### 思路
首先可以觀察到棋盤的大小$n\times n(n\le 50)$很小,所以可以嘗試暴力枚舉。
不過雖說暴力枚舉,也不能太過暴力。例如枚舉$(2n+1)\times (2n+1)$盤面中的所有可能,這樣最多要做$3^{(2n+1)\times (2n+1)}=3^{101^2}$,做得完才有鬼。
一個可能的思考方向是對於每個 $x$ ,枚舉他們屬於哪個 $o$ ,但評估這樣做法的時間複雜度,會發現 $x$ 和 $o$ 的量級都為$O{(n^2)}$,然後枚舉 $x$ 是否屬於當前的 $o$ 時,又要再枚舉所有的 $o$ 來驗證是否符合。所以時間複雜度為$O{(cnt(x)\times cnt(o)\times cnt(x)}=O{((n^2)\times (n^2)\times (n^2))}=O{(n^6)}$,套入這題的最大$n$,大約是$15625000000$,時間上會被卡掉。
這時可以"換個方向思考",枚舉旗子不能移動到哪些位置,然後再驗證這樣的解是否能到達所有的 $x$ 。
這種做法的實作可以先假設棋子可以移動到任何地方,然後再枚舉所有的 $.$ 和 $o$ ,因為由 $o$ 指向 $.$ 的移動向量必定不能是 $x$ ,所以就可以排除它的可能性,將其換成 $.$ 。最後就可以得到一個保證不會踩到 $.$ 的解,不過這樣仍不能確定它能觸及所有 $x$ ,所以另外枚舉所有的 $o$ 和所有的 $x$ ,確認對於每個 $x$ ,至少存在一枚棋子 $o$ 能移動過來,如果這步驟通過則答案為"YES";反之為"NO"。這樣的時間複雜度為$O{(n^4)}$
### code
```cpp=
#pragma GCC optimize("O3,unroll-loops")
#pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt")
#include <bits/stdc++.h>
#define F first
#define S second
#define SZ(x) (int)(x).size()
using namespace std;
using pii = pair<int, int>;
char board[55][55], ans[105][105];
void solve() {
int n;
cin >> n;
vector<pii> chess, attacked;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cin >> board[i][j];
if (board[i][j] == 'o') chess.emplace_back(i, j);
if (board[i][j] == 'x') attacked.emplace_back(i, j);
}
}
const int len = 2 * n - 1, mid = len / 2;
for (int i = 0; i < len; ++i) {
for (int j = 0; j < len; ++j) {
ans[i][j] = 'x';
}
}
ans[mid][mid] = 'o';
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (board[i][j] != '.') continue;
for (int k = 0; k < SZ(chess); ++k) {
pii d {mid + (i - chess[k].F), mid + (j - chess[k].S)};
if (d.F >= 0 && d.F < len && d.S >= 0 && d.S < len) {
ans[d.F][d.S] = '.';
}
}
}
}
for (int i = 0; i < SZ(attacked); ++i) {
bool ok = false;
for (int j = 0; j < SZ(chess); ++j) {
if (ans[mid + attacked[i].F - chess[j].F][mid + attacked[i].S - chess[j].S] == 'x') ok = true;
}
if (!ok) {
cout << "NO\n";
return;
}
}
cout << "YES\n";
for (int i = 0; i < len; ++i) {
for (int j = 0; j < len; ++j) {
cout << ans[i][j];
}
cout << "\n";
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
solve();
}
```
###### tags: `每日CF梗題挑戰`