Codeforces Round #812 E - Cross Swapping 题解

首先发现无论如何操作,只有关于对角线对称的两个位置(ai,j,aj,ia_{i, j}, a_{j, i})会被交换,而且只有k=ik=ik=jk=j的操作会影响这两个位置的交换。 我们用sis_i来表示操作k=ik=i是否被执行,如果ai,j<aj,ia_{i, j} < a_{j, i},我们不希望他们被交换所以我们希望sisj=0s_i\oplus s_j=0,反之如果ai,j>aj,ia_{i, j} > a_{j, i},我们希望他们交换所以我们希望sisj=1s_i \oplus s_j = 1。(\oplus代表按位异或)

对于字典序的问题,往往采用从前往后贪心的策略。对于当前的i,ji, j我们要看之前的限制是否允许我们希望的sisjs_i \oplus s_j值。那么如何判断呢?我们用一种让并查集的边带权的技巧,即边(u,v)(u, v)的权值代表susvs_u\oplus s_v,那么一个分量中任意两点的异或值即为路径上边的异或值。为了方便实现我们用节点uu代表uu到其父节点的边。在findjoin时要更新边权(见代码)。

条件sisj=xs_i \oplus s_j = x就相当于给iijj中间连一条权值为xx的边,对应并查集的join操作,如果加边之前两个点不在同一个连通分量,那么条件一定是可以满足的。如果在同一个分量中,那么说明sisjs_i \oplus s_j已经是确定的,如果sisjxs_i \oplus s_j \neq x说明当前条件不能满足,我们就跳过它。

其他实现细节见代码:

#include <bits/stdc++.h>
using namespace std;
 
struct UF {
    vector<int> fa, sz, parity;
    UF(int n) : fa(n), sz(n, 1), parity(n) { iota(fa.begin(), fa.end(), 0); }
 
    array<int, 2> find(int x) {
        if (fa[x] == x) {
            return {x, 0};
        }
        auto [f, z] = find(fa[x]);
        fa[x] = f;
        parity[x] ^= z;
        return {fa[x], parity[x]};
    }
 
    bool same(int x, int y) { return find(x) == find(y); }
 
    bool join(int x, int y, int p) {
        auto [fx, px] = find(x);
        auto [fy, py] = find(y);
        if (fx == fy) 
            return (px ^ py ^ p) == 0;
        if (sz[fx] > sz[fy]) swap(fx, fy);
        fa[fx] = fy;
        parity[fx] = px ^ py ^ p;
        sz[y] += sz[x];
        return true;
    }
};
 
void test_case() {
    int n;
    cin >> n;
    vector a(n, vector(n, 0));
    for (auto& v : a) {
        for (auto& x : v) {
            cin >> x;
        }
    }
    UF uf(n);
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            if (a[i][j] == a[j][i]) {
                continue;
            }
            if (uf.join(i, j, a[i][j] > a[j][i]) ^ (a[i][j] < a[j][i])) {
                swap(a[i][j], a[j][i]);
            }
        }
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cout << a[i][j] << " \n"[j == n - 1];
        }
    }
};
 
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int tt = 1;
    cin >> tt;
    while (tt--) {
        test_case();
    }
}