zoukankan      html  css  js  c++  java
  • [算法学习] 长链剖分

    简介

    长链剖分是跟dsu on tree类似的小(trick),可以资瓷维护子树中只与深度有关的信息
    并能达到线性的时间复杂度。

    算法流程

    对于每个点,记录重儿子(heavy[u])表示深度最大的儿子,其余作为轻儿子
    这样我们可以得到若干条互不相交的长链。
    在维护信息的过程中,我们先(O(1))继承重儿子的信息,再暴力合并其余轻儿子的信息。
    因为每一个点属于一条长链,且一条长链只会在链顶位置作为轻儿子暴力合并一次,所以复杂度是线性的。
    但是我们发现,这个数组仿佛开不下(大雾),所以我们需要想想办法来解决。
    有一个比较巧妙的方法,就是利用指针来实现。
    下面以一道题为例。

    CF1009F Dominant Indices

    题目链接:CF1009F Dominant Indices

    Description

    给定一个以 (1) 为根, (n) 个节点的树。
    (d(u,x))(u) 子树中到 (u) 距离为 (x) 的节点数。
    求对于每一个点,最小的 (k) ,使得 (d(u,k)) 最大。
    数据范围 (1le nle 10^6)

    Solution

    我们先考虑如何暴力做。
    定义(f_{u,i})表示在 (u) 的子树内,到 (u) 的距离为 (i) 的点的个数。
    那么,我们不难推出转移方程: (f_{u,0}=1,f_{u,i}=sum_{vin son(x)}f_{v,i-1})
    复杂度:(O(n^2)),需要进行优化。
    我们定义(heavy[u])表示深度最大的儿子,(len[u])表示(x)到儿子的最长距离。
    不难发现(dp)第二维的 (i) 肯定不超过 (len[u])
    为避免数组存不下的问题,我们采用指针来代替,即对于每条长链的链顶给它一个长度为(len[x])的内存。
    这样的好处在于,对于一条长链,我们可以直接让父亲点从子节点那里继承答案。
    对于非重儿子的点,我们暴力合并链即可。
    很显然,每条链只会被合并一次,因此复杂度是线性的。
    复杂度:(O(n)),可以通过本题。

    Code

    解释一下(id)的作用:我们对于每条长链,给它开一个(len[x])的内存,所以(id)是给每一个(f[u])提供一段内存用的。
    因为只在链顶会开内存,且每个点仅在一条重链内,所以空间复杂度是(O(n))的。
    但是实现起来,常数的确很大!(n=1e6)的时候大概要(100+)ms。

    // Author: wlzhouzhuan
    #pragma GCC optimize(2)
    #pragma GCC optimize(3)
    #include <bits/stdc++.h>
    using namespace std;
    
    #define ll long long
    #define ull unsigned long long
    #define rint register int
    #define rep(i, l, r) for (rint i = l; i <= r; i++)
    #define per(i, l, r) for (rint i = l; i >= r; i--)
    #define mset(s, _) memset(s, _, sizeof(s))
    #define pb push_back
    #define pii pair <int, int>
    #define mp(a, b) make_pair(a, b)
    
    inline int read() {
      int x = 0, neg = 1; char op = getchar();
      while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
      while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
      return neg * x;
    }
    inline void print(int x) {
      if (x < 0) { putchar('-'); x = -x; }
      if (x >= 10) print(x / 10);
      putchar(x % 10 + '0');
    }
    
    const int N = 1000005;
    vector <int> adj[N];
    void add(int u, int v) { adj[u].pb(v); }
    int n;
    
    int heavy[N], len[N];
    void dfs1(int u, int fa) {
      for (auto v: adj[u]) {
        if (v == fa) continue;
        dfs1(v, u);
        if (len[v] > len[heavy[u]]) heavy[u] = v;
      }
      len[u] = len[heavy[u]] + 1;
    }
    int *f[N], tmp[N], *id = tmp, ans[N];
    void dfs2(int u, int fa) {
      f[u][0] = 1;
      if (heavy[u]) {
        f[heavy[u]] = f[u] + 1;
        dfs2(heavy[u], u);
        ans[u] = ans[heavy[u]] + 1;
      }
      for (auto v: adj[u]) {
        if (v == fa || v == heavy[u]) continue;
        f[v] = id, id += len[v];
        dfs2(v, u);
        for (rint j = 1; j <= len[v]; j++) {
          f[u][j] += f[v][j - 1];
          if (f[u][j] > f[u][ans[u]] || (f[u][j] == f[u][ans[u]] && j < ans[u])) {
            ans[u] = j;      
          }
        }    
      }
      if (f[u][ans[u]] == 1) {
        ans[u] = 0;
      }
    }
    
    int main() {
      n = read();
      for (rint i = 1; i < n; i++) {
        int u = read(), v = read();
        add(u, v), add(v, u);
      }
      int root = 1; // 题目定义根为1 
      dfs1(root, 0);
      f[1] = id, id += len[1];
      dfs2(root, 0);
      for (rint i = 1; i <= n; i++) {
        printf("%d
    ", ans[i]);
      }
      return 0;
    }
    
  • 相关阅读:
    [模板] 主席树
    [模板] 替罪羊树
    [模板] Treap
    [LUOGU] P4342 [IOI1998]Polygon
    [JOYOI] 1051 选课
    poj 1845 数论(唯一分解定理+分治法求等比数列前n项的和mod m的值)
    poj 2418 bst统计字符串
    hdu 3791 二叉排序树
    hdu 3999 二叉排序树
    toj 3711 水题
  • 原文地址:https://www.cnblogs.com/wlzhouzhuan/p/12590088.html
Copyright © 2011-2022 走看看