CF1867F Most Different Tree 记录
题目链接:https://codeforces.com/contest/1867/problem/F
题意简述
记 \(P(T)\) 为一棵树 \(T\) 的所有子树的集合。给定一棵 \(n\) 个点的树 \(T\),找出点数相同的树 \(T'\),使 \(P(T')\) 中“与 \(P(T)\) 中至少一棵树同构”的树数量最少。\(n \le 10^6\)。
题解(官解)
这题的解法有一点构造的元素。给出下面的命题:
找到大小最小的(记为 \(sz\))不与 \(P(T)\) 中任何一棵树同构的树 \(H\),则在一个 \(n - sz\) 条边的链的最下端接上 \(H\) 即为一个合法的答案。\(H\) 的任何非自身的子树都与 \(P(T)\) 中至少一棵树同构,\(P(T')\) 中任何其它子树都不与 \(P(T)\) 中任何一棵树同构,因此数量 \(f(T')\) 为 \(sz - 1\)。
下面证明这是最优解。对任何一个最优解 $ T^* $(记 \(f(T^* ) = x\)),找到其中大小最小的“不与 \(P(T)\) 中任何一棵树同构”的子树,则它的大小不超过 \(x + 1\),否则答案会超过 \(x\)。因此按照上面的方法构造的答案不超过 \(x\),因此也是最优解。
因此,问题的关键就在于找到树 \(H\)。大小为 \(n\) 的,固定根无标号树的数量可以在 OEIS 上查到。其中第 \(15\) 项为 \(87811\)。由于 $15 * 87811 > 10^6 $,只要搜索所有大小不超过 \(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";
}
 
                    
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号