zoukankan      html  css  js  c++  java
  • bzoj4754 [JSOI2016]独特的树叶

    bzoj4754 [JSOI2016]独特的树叶

    给定两棵无根树 (A, B) ,其中 (B) 恰好是由 (A) 添加一个叶子,然后将节点的编号打乱得到的,求这个被添加的叶子在 (B) 中的编号(若有多个符合要求的叶子,输出编号最小的)。

    (|A|leq10^5)

    hash,dp,二次换根


    考虑使用树hash来快速比较,我以前用的hash式子是 (hash_u=(displaystylesum_{vin ext{son(u)}}base^{k-i-1} imes hash_u+hash_v)+1) ,但这样错误率很高(很容易有两个形态不同的树hash值相同),可以考虑将式子中的 (1) 以与树的形态有关的值替代,这道题可以使用 ( ext{size(u)})

    可以考虑求出 (A, B) 以每个节点为根时的 hash 值,并枚举 (B) 的所有叶子,并快速求出去掉该叶子后整棵树的hash值。按照上面的式子,若以叶子 (u) 为根的hash值为 (x) ,那么去掉 (u) 后整棵树的hash值为 (x-|B|)

    我们可以 (O(n)) 求出每个点子树的hash值,记为 (f_i) ,记 (g_i) 为以 (i) 为根的hash值,可以考虑使用二次换根(自顶向下)。

    令当前节点为 (v) ,该节点的父亲为 (u)(g_v) 相当于 (f_v) 多了一个 “以 (u) 为根去掉儿子 (v) 的hash值” 的儿子,暴力枚举 (u) 为根时的所有儿子可以被菊花图卡掉,因此需要化一下式子。可以记录下以每个点为根时的所有儿子的hash值 (val_{u, i}) ,并维护这时的 “hash前缀和” (sum_{u, k}=displaystylesum_{i=0}^kval_{u, i} imes base^{n-i-1}) 。“以 (u) 为根去掉儿子 (v) 的hash值” 就相当于求一个序列删掉一个点后的hash值。令 (n= ext{size(}val_u ext{)}) ,去掉位置 (k) 后的hash值即为 (sum_{u, n}-(sum_{u, k}-sum_{u, k-1}) imes base^{n-k}) 。求出 “以 (u) 为根去掉儿子 (v) 的hash值” 后扫一遍 (v) 的儿子就可以处理出 (g_v, val_v, sum_v) 了。时间复杂度 (O(nlog n))

    可以发现hash式子中的最后一项 ( ext{size(u)}) 既满足错误率低,又满足转移方便,因此这是树hash的一个不错的选择(雾

    时间复杂度 (O(nlog n))

    代码

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef unsigned long long u64;
    const int maxn = 1e5 + 10;
    const u64 base = 19260817, Inv = 7089841341079321457ull;
    int n;
    u64 pw[maxn];
    
    struct Tree {
      int N, sz[maxn];
      u64 dp1[maxn], dp2[maxn];
    
      vector <int> e[maxn];
      vector <u64> vec[maxn], sum[maxn];
    
      u64 dfs1(int u, int f) {
        sz[u] = 1;
        for (int v : e[u]) {
          if (v != f) vec[u].push_back(dfs1(v, u)), sz[u] += sz[v];
        }
        sort(vec[u].begin(), vec[u].end());
        int SZ = vec[u].size(); sum[u].resize(SZ);
        for (int i = 0; i < SZ; i++) {
          sum[u][i] = (i ? base * sum[u][i - 1] : 0) + vec[u][i];
        }
        return dp1[u] = (SZ ? sum[u].back() : 0) + sz[u];
      }
    
      void dfs2(int u, int f) {
        int SZ = vec[f].size();
        int pos = distance(vec[f].begin(), lower_bound(vec[f].begin(), vec[f].end(), dp1[u]));
        vec[u].push_back(sum[f][SZ - 1] - (sum[f][pos] - (pos ? sum[f][pos - 1] : 0)) * pw[SZ - pos - 1] + N - sz[u]);
        sort(vec[u].begin(), vec[u].end());
        SZ = vec[u].size(), sum[u].resize(SZ);
        for (int i = 0; i < SZ; i++) {
          sum[u][i] = (i ? base * sum[u][i - 1] : 0) + vec[u][i];
        }
        dp2[u] = sum[u].back() + N;
        for (int v : e[u]) {
          if (v != f) dfs2(v, u);
        }
      }
    
      void build() {
        for (int i = 1; i < N; i++) {
          int u, v;
          scanf("%d %d", &u, &v);
          e[u].push_back(v), e[v].push_back(u);
        }
        dfs1(1, 0);
        dp2[1] = dp1[1];
        for (int v : e[1]) {
          dfs2(v, 1);
        }
      }
    } A, B;
    
    set <u64> S;
    
    int main() {
      pw[0] = 1;
      for (int i = 1; i < 100005; i++) {
        pw[i] = base * pw[i - 1];
      }
      scanf("%d", &n);
      A.N = n, B.N = n + 1;
      A.build(), B.build();
      for (int i = 1; i <= A.N; i++) {
        S.insert(A.dp2[i]);
      }
      for (int i = 1; i <= B.N; i++) {
        if (B.e[i].size() > 1) continue;
        u64 x = B.dp2[i] - B.N;
        if (S.find(x) != S.end()) {
          printf("%d", i); return 0;
        }
      }
      assert(0);
      return 0;
    }
    
  • 相关阅读:
    STRIDE威胁分析与DREAD威胁评价
    HashMap 几大问题
    java 集合中的错误检测机制
    科创人·StreamNative翟佳:开源模式价值为王,基础软件的未来在国内社区
    科创人·云柚智能CEO汤峥嵘:价值观一致奠定共事基础,技术创新加速行业变革
    科创人·微软中国CTO韦青:数智时代创业得跳下巨人肩膀,还需掌握基础知识和逻辑能力
    科创人研习社·微智云CEO 张虎:从CTO到创始人关键是扩大视野半径
    科创人·天云数据CEO雷涛:打造正确理解数智的认知体系
    neovim环境与vim简单使用
    MIT6.828——Lab3 PartA(麻省理工操作系统实验)
  • 原文地址:https://www.cnblogs.com/Juanzhang/p/11332632.html
Copyright © 2011-2022 走看看