CF1867F Most Different Tree 记录

题目链接:https://codeforces.com/contest/1867/problem/F

题意简述

P(T) 为一棵树 T 的所有子树的集合。给定一棵 n 个点的树 T,找出点数相同的树 T,使 P(T) 中“与 P(T) 中至少一棵树同构”的树数量最少。n106

题解(官解)

这题的解法有一点构造的元素。给出下面的命题:

找到大小最小的(记为 sz)不与 P(T) 中任何一棵树同构的树 H,则在一个 nsz 条边的链的最下端接上 H 即为一个合法的答案。H 的任何非自身的子树都与 P(T) 中至少一棵树同构,P(T) 中任何其它子树都不与 P(T) 中任何一棵树同构,因此数量 f(T)sz1

下面证明这是最优解。对任何一个最优解 T(记 f(T)=x),找到其中大小最小的“不与 P(T) 中任何一棵树同构”的子树,则它的大小不超过 x+1,否则答案会超过 x。因此按照上面的方法构造的答案不超过 x,因此也是最优解。

因此,问题的关键就在于找到树 H。大小为 n 的,固定根无标号树的数量可以在 OEIS 上查到。其中第 15 项为 87811。由于 1587811>106,只要搜索所有大小不超过 15 的树即可。对是否与 P(T) 中树同构的判断,可以采用树哈希,先一次遍历计算出 P(T) 中所有哈希值,之后搜索时即可 O(1) 判断。

所有大小为 n 的子树的搜索比较玄学,这里我选择直接枚举根节点的所有子树,可以使用从小到大枚举的方式剪枝。

代码实现(C++)

#include <bits/stdc++.h>
using namespace std;
using i64 = int64_t;
using u64 = uint64_t;
static uint64_t splitmix64(uint64_t x) {
static const uint64_t FIXED_RANDOM =
chrono::steady_clock::now().time_since_epoch().count();
x += 0x9e3779b97f4a7c15;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
return FIXED_RANDOM ^ x ^ (x >> 31);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
set<u64> vis;
vector<pair<int, int>> e;
{
vector<vector<int>> adj(n + 1);
for (int ei = 1; ei < n; ei++) {
int u, v;
cin >> u >> v;
e.push_back({u, v});
adj[u].push_back(v);
adj[v].push_back(u);
}
function<u64(int, int)> dfs = [&](int u, int fa) {
u64 h = 1;
for (int v : adj[u]) {
if (v != fa) { h += splitmix64(dfs(v, u)); }
}
vis.insert(h);
return h;
};
dfs(1, -1);
}
vector<vector<int>> sons(0);
vector<u64> h(0);
vector<vector<int>> sz_root(16);
int cnt = 0;
auto print_ans = [&](int sz, int u) {
vector<int> mp(cnt);
for (int i = 1; i <= n - sz; i++) {
cout << i << " " << i + 1 << "\n";
}
int t = n - sz;
function<void(int)> dfs = [&](int u) {
mp[u] = ++t;
for (int v : sons[u]) {
dfs(v);
cout << mp[u] << " " << mp[v] << "\n";
}
};
dfs(u);
exit(0);
};
for (int sz = 1; sz <= n; sz++) {
vector<int> tmpsons;
function<void(int, int, u64)> dfs = [&](int now_sz, int min_add_sz,
u64 now_hash) {
if (now_sz == sz) {
sz_root[sz].push_back(cnt);
cnt++;
sons.push_back(tmpsons);
h.push_back(now_hash);
if (!vis.contains(now_hash)) { print_ans(sz, cnt - 1); }
return;
} else if (now_sz > sz) return;
for (int add_sz = min_add_sz; add_sz + now_sz <= sz; add_sz++) {
for (int v : sz_root[add_sz]) {
tmpsons.push_back(v);
dfs(now_sz + add_sz, add_sz, now_hash + splitmix64(h[v]));
tmpsons.pop_back();
}
}
};
dfs(1, 1, 1);
}
for (auto [u, v] : e)
cout << u << " " << v << "\n";
}
posted @   cccpchenpi  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示