---
# System prepended metadata

title: CF 538 D. Weird Chess
tags: [每日CF梗題挑戰]

---

# 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梗題挑戰`