LOJ3137 [COI2019] IZLET
非常好构造题,爱来自瓷器。
记矩阵为 ,不妨从第一个 subtask 开始思考,考察 和 的 和 有什么性质。
不难发现 和 当且仅当在同一个同色极大连通块中时,满足 。
对于合法的树上的每一个同色极大连通块,一定可以选出任意一个代表结点,然后把其他结点类似菊花图一样挂在关键点上,在原连通块相邻的两个关键点间连边。易证调整之后树依然合法。
于是用并查集把同色极大连通块提取出来,选择根作为关键点。
下文的树都是指关键点构成的树。
考虑树的形态,此时相邻两点颜色不同。
我们给每条边分配一个无序二元组的颜色 表示这条边的两个端点的颜色。
我们把共端点且颜色相同的边分配到一个等价类中,容易发现树被分成了若干个连通块。
相邻两个连通块的点集交集大小为 ,定义这个点属于两个连通块的边界。
关键点 和 当且仅当在同一个连通块中时,满足 。
不难发现,同一个连通块中的连边是任意的,即在每一个连通块的点集构成的完全图中任取一棵生成树的都能合法。
考虑证明。
连通块内的点显然能重新黑白染色,进而使点对满足限制。连通块外的点对不受影响,连通块内非边界的点和连通块外的点也不受影响。
边界上的点颜色可能改变。我们以当前的连通块为根,将每个连通块看作结点,形成一棵树。若点 从颜色 变成颜色 ,把 所在的另一个连通块以及其子树中的颜色 全部变为颜色 。容易发现这不影响合法性。
于是得证。
所以用另一个并查集维护连边过程。这样树的结构已经确定,只要考虑染色。
记 路径上离 最近的点为 ,离 最近的点为 。当且仅当 时, 同色且它们间的路径上没有其他和 同色的点。
所以直接从每个点开始 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;
}
分类:
图论
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通