LOJ3137 [COI2019] IZLET

非常好构造题,爱来自瓷器。

记矩阵为 \(a\),不妨从第一个 subtask 开始思考,考察 \(a_{i, j} = 1\)\(a_{i, j} = 2\)\(i\)\(j\) 有什么性质。

不难发现 \(i\)\(j\) 当且仅当在同一个同色极大连通块中时,满足 \(a_{i, j} = 1\)

对于合法的树上的每一个同色极大连通块,一定可以选出任意一个代表结点,然后把其他结点类似菊花图一样挂在关键点上,在原连通块相邻的两个关键点间连边。易证调整之后树依然合法。

于是用并查集把同色极大连通块提取出来,选择根作为关键点。

下文的树都是指关键点构成的树。

考虑树的形态,此时相邻两点颜色不同。

我们给每条边分配一个无序二元组的颜色 \((x, y)\) 表示这条边的两个端点的颜色。

我们把共端点且颜色相同的边分配到一个等价类中,容易发现树被分成了若干个连通块。

相邻两个连通块的点集交集大小为 \(1\),定义这个点属于两个连通块的边界。

关键点 \(i\)\(j\) 当且仅当在同一个连通块中时,满足 \(a_{i, j} = 2\)

不难发现,同一个连通块中的连边是任意的,即在每一个连通块的点集构成的完全图中任取一棵生成树的都能合法。

考虑证明。

连通块内的点显然能重新黑白染色,进而使点对满足限制。连通块外的点对不受影响,连通块内非边界的点和连通块外的点也不受影响。

边界上的点颜色可能改变。我们以当前的连通块为根,将每个连通块看作结点,形成一棵树。若点 \(u\) 从颜色 \(x\) 变成颜色 \(y\),把 \(u\) 所在的另一个连通块以及其子树中的颜色 \(x\) 全部变为颜色 \(y\)。容易发现这不影响合法性。

于是得证。

所以用另一个并查集维护连边过程。这样树的结构已经确定,只要考虑染色。

\(u, v\) 路径上离 \(u\) 最近的点为 \(w_1\),离 \(v\) 最近的点为 \(w_2\)。当且仅当 \(a_{u, v} = a_{u, w_2} = a_{w_1, v} = a_{w_1, w_2} + 1\) 时,\(u, v\) 同色且它们间的路径上没有其他和 \(u, v\) 同色的点。

所以直接从每个点开始 dfs,用第三个并查集维护颜色相同的点集。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 3010;
int n, a[MAXN][MAXN], col[MAXN];
vector<int> g[MAXN];
vector<pair<int, int>> ans;
struct ufset {
    int fa[MAXN];
    void init() { iota(fa + 1, fa + n + 1, 1); }
    int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
    bool merge(int x, int y) {
        x = find(x), y = find(y);
        if (x == y) return false;
        fa[x] = y;
        return true;
    }
} s1, s2, s3;
void dfs(int u, int pre, int son, int rt) {
    if (son != u && a[u][son] != a[pre][son] && a[pre][rt] != a[pre][son] &&
        a[u][son] == a[u][rt]) {
        s3.merge(u, rt);
    }
    for (auto v : g[u]) {
        if (v == pre) continue;
        dfs(v, u, son, rt);
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int ccf;
    cin >> ccf >> n;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j) cin >> a[i][j];
    s1.init();
    for (int i = 1; i <= n; ++i) {
        for (int j = i + 1; j <= n; ++j)
            if (a[i][j] == 1) s1.merge(i, j);
    }
    s2.init();
    for (int i = 1; i <= n; ++i) {
        if (s1.fa[i] != i) continue;
        for (int j = i + 1; j <= n; ++j) {
            if (s1.fa[j] != j || a[i][j] != 2) continue;
            if (s2.merge(i, j)) {
                g[i].emplace_back(j);
                g[j].emplace_back(i);
                ans.emplace_back(i, j);
            }
        }
    }
    s3.init();
    for (int u = 1; u <= n; ++u) {
        if (s1.fa[u] != u) continue;
        for (auto v : g[u]) dfs(v, u, v, u);
    }
    int cnt = 0;
    for (int u = 1; u <= n; ++u)
        if (s1.fa[u] == u && s3.fa[u] == u) col[u] = ++cnt;
    for (int u = 1; u <= n; ++u)
        if (s1.fa[u] == u) col[u] = col[s3.find(u)];
    for (int u = 1; u <= n; ++u)
        if (s1.fa[u] != u) col[u] = col[s1.find(u)];
    for (int i = 1; i <= n; ++i) cout << col[i] << " \n"[i == n];
    for (auto &i : ans) cout << i.first << " " << i.second << "\n";
    for (int u = 1; u <= n; ++u)
        if (s1.fa[u] != u) cout << u << " " << s1.find(u) << "\n";
    return 0;
}
posted @ 2022-10-09 12:28  JCY_std  阅读(125)  评论(0编辑  收藏  举报