1 条题解

  • 0
    @ 2026-10-27 13:20:36

    思路与算法

    一棵有根树的形状由各儿子子树形状的多重集唯一决定。建立全局形状编号器:空多重集对应叶子;每个多重集用固定编号值域上的持久线段树精确表示。叶子存该形状出现次数,内部节点按左右孩子ID全局驻留,相同孩子对一定共用ID。哈希表只是查找完整孩子对,发生哈希碰撞仍比较完整整数键。先后序计算向下分支形状,再换根计算父侧分支形状和每个顶点作根的全树形状。将A全部根形状放入集合。对B每个叶子x,其邻点v的完整邻居多重集中删去一个“单叶形状”,得到B-x以v为根的形状;若属于A的根形状集合,x可删除,按原编号升序找第一个。

    正确性说明

    对有根树节点数归纳:单叶为空多重集;一般根树同构当且仅当各儿子子树可按形状一一匹配,等价于儿子形状多重集相同。规范化线段树对每个编号保存精确次数,内部ID由完整孩子对唯一确定,因此多重集ID相同当且仅当内容相同,得到无碰撞的精确形状编号。换根时删去某儿子贡献,剩余多重集恰为从该儿子看父侧分支;加上父侧贡献得到当前顶点的完整根形状。删B的叶子仅移除邻点的一个单叶分支,其余结构不变,故上述减少后的根形状准确。无根树同构必使B-x的根v对应A中的某个点;反之若与A某根形状相同,忘记根仍然同构,所以集合判定充要。升序枚举保证最小编号。

    复杂度

    设n=N,形状编号范围R=2^20。两棵树的子树、父侧分支和全树形状总数少于7n+10,满足固定值域。建树、数组与遍历初始化O(n),持久多重集每次修改O(log R),修改次数O(n),总为期望O(n log R)时间(精确结构哈希索引)和O(n log R)空间。哈希索引表容量2^24,初始化也需O(2^24)时间和空间;此外预留节点容量14000010及形状索引容量,均为题目n≤10^5的显式工程常数,不可忽略其资源。没有递归深树遍历,只有20层值域修改递归。

    易错点

    无标号同构不能比较原节点编号或只比较度数。相同儿子形状可以多次出现,必须维护次数而非集合。删叶时减的是邻点的单叶分支;不能只比较B叶子的原子树编号。线段树内部驻留比较完整孩子ID,不能把有限模数哈希值直接视为同构证据。全局编号器供A/B共同使用;N=1也应正确处理。

    参考代码(C++11)

    #include <bits/stdc++.h>
    #include <unordered_set>
    using namespace std;
    struct Node {
        int l, r;
    };
    struct Canonical {
        vector<Node> nodes;
        vector<int> table;
        unordered_map<int, int> shapes;
        int shapeCount = 1;
        Canonical() : table(1 << 24, 0) {
            nodes.reserve(14000010);
            nodes.push_back({0, 0});
            shapes.reserve(800000);
            shapes[0] = 1;
        }
        static unsigned long long hash64(unsigned long long x) {
            x ^= x >> 30;
            x *= 0xbf58476d1ce4e5b9ULL;
            x ^= x >> 27;
            x *= 0x94d049bb133111ebULL;
            return x ^ (x >> 31);
        }
        int intern(int l, int r) {
            if (!l && !r)
                return 0;
            unsigned long long key = ((unsigned long long)(unsigned int)l << 32) | (unsigned int)r;
            unsigned pos = hash64(key) & (table.size() - 1);
            while (table[pos]) {
                int id = table[pos];
                if (nodes[id].l == l && nodes[id].r == r)
                    return id;
                pos = (pos + 1) & (table.size() - 1);
            }
            int id = nodes.size();
            nodes.push_back({l, r});
            table[pos] = id;
            return id;
        }
        int update(int root, int pos, int delta, int level = 19) {
            if (level < 0) {
                int count = -root + delta;
                assert(count >= 0);
                return -count;
            }
            int l = root ? nodes[root].l : 0, r = root ? nodes[root].r : 0;
            if (pos >> level & 1)
                r = update(r, pos, delta, level - 1);
            else
                l = update(l, pos, delta, level - 1);
            return intern(l, r);
        }
        int shape(int root) {
            auto it = shapes.find(root);
            if (it != shapes.end())
                return it->second;
            assert(shapeCount + 1 < (1 << 20));
            return shapes[root] = ++shapeCount;
        }
    };
    struct Tree {
        int n;
        vector<vector<int>> g;
        vector<int> parent, order, sub, multi, up, full, rootShape;
        Tree(int n) : n(n), g(n), parent(n, -1), sub(n), multi(n), up(n), full(n), rootShape(n) {
        }
        void read() {
            for (int i = 1, a, b; i < n; i++) {
                cin >> a >> b;
                --a;
                --b;
                g[a].push_back(b);
                g[b].push_back(a);
            }
        }
        void process(Canonical &c) {
            order.push_back(0);
            parent[0] = 0;
            for (size_t k = 0; k < order.size(); k++) {
                int v = order[k];
                for (int u : g[v])
                    if (u != parent[v])
                        parent[u] = v, order.push_back(u);
            }
            for (int k = n - 1; k >= 0; k--) {
                int v = order[k], r = 0;
                for (int u : g[v])
                    if (u != parent[v])
                        r = c.update(r, sub[u], 1);
                multi[v] = r;
                sub[v] = c.shape(r);
            }
            for (int v : order) {
                int r = multi[v];
                if (v)
                    r = c.update(r, up[v], 1);
                full[v] = r;
                rootShape[v] = c.shape(r);
                for (int u : g[v])
                    if (u != parent[v])
                        up[u] = c.shape(c.update(r, sub[u], -1));
            }
        }
    };
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int n;
        if (!(cin >> n))
            return 0;
        Tree a(n), b(n + 1);
        a.read();
        b.read();
        Canonical c;
        a.process(c);
        b.process(c);
        unordered_set<int> target(a.rootShape.begin(), a.rootShape.end());
        for (int i = 0; i <= n; i++)
            if (b.g[i].size() == 1) {
                int neighbor = b.g[i][0];
                int reduced = c.update(b.full[neighbor], 1, -1);
                int shape = c.shape(reduced);
                if (target.count(shape)) {
                    cout << i + 1 << '\n';
                    return 0;
                }
            }
        cout << "-1\n";
    }
    
    • 1

    信息

    ID
    2111
    时间
    1000ms
    内存
    256MiB
    难度
    提高+
    标签
    递交数
    0
    已通过
    0
    上传者