zoukankan      html  css  js  c++  java
  • 【模板】K级祖先(长链剖分)

    题目:lxhgww的奇思妙想——长链剖分

    思路

      长链剖分是根据链的长度为准则对一棵树进行剖分,和树剖类似。它有着特殊之处:

      (1)、从一个结点开始往上跳,经过的长链不超过( ext{O}(sqrt n))次。这个就没有树剖优秀了。

      (2)、从一个结点向上跳(k)层,跳到的结点所在长链的长度(>k)。这点很显然,但却很有用。用这一点我们可以( ext{O}(nlog n))预处理,( ext{O}(1))在线查询(K)级祖先。

      假设给定(K),我们求出(t=lfloor log_2 K floor),先利用倍增数组一次跳(2^t)层,再从该结点向上跳剩下的(K-2^t)层,我们可以维护每一条长链以及该长链从链顶处维护一个向上拓展的长度为(K)的链,如果跳完这些还在长链上,就直接在长链上跳,否则直接在链顶处拓展的链上往上跳,由于第二点性质,能够保证每次一定不会跳出拓展出来的链。

      是不是非常巧妙!

    代码

    #include <bits/stdc++.h>
    
    using namespace std;
    
    #define ll long long
    #define ull unsigned long long
    #define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
    #define per(i, a, b) for (int i = a, i##end = b; i >= i##end; --i)
    #define rep0(i, a) for (int i = 0, i##end = a; i < i##end; ++i)
    #define per0(i, a) for (int i = a-1; ~i; --i)
    #define chkmax(a, b) a = max(a, b)
    #define chkmin(a, b) a = min(a, b)
    
    const int inf = 0x3fffffff;
    const int maxn = 311111;
    
    inline int read() {
    	int w = 0; char c;
    	while (!isdigit(c = getchar())) ;
    	while (isdigit(c)) w = (w << 3) + (w << 1) + (c ^ 48), c = getchar();
    	return w;
    }
    
    int n;
    
    struct Edge {
    	int v, nxt;
    } e[maxn << 1];
    int G[maxn], edges;
    void clear() {
    	memset(G, -1, sizeof G); edges = 0;
    }
    void adde(int u, int v) {
    	e[edges++] = (Edge){v, G[u]}; G[u] = edges-1;
    }
    
    int par[20][maxn], mxd[maxn], son[maxn], dep[maxn], top[maxn]; // 维护2^k的祖先、最深的长链、长脸上的孩子结点、自身深度、链顶。类似于重链剖分
    vector<int> up[maxn], chain[maxn]; // 记录长链以及向上跳的链
    
    void dfs1(int u) {
    	mxd[u] = dep[u] = dep[par[0][u]] + 1, son[u] = 0;
    	for (int i = 1; 1<<i <= n; i++) par[i][u] = par[i-1][par[i-1][u]]; // 倍增
    	for (int i = G[u], v; ~i; i = e[i].nxt)
    		if ((v = e[i].v) != par[0][u])
    			par[0][v] = u, dfs1(v), mxd[v] > mxd[u] && (mxd[u] = mxd[v], son[u] = v); // 与树剖不同的地方在于比较关键字为深度而不是子树大小
    }
    
    void dfs2(int u, int tp) {
    	top[u] = tp;
    	if (son[u]) dfs2(son[u], tp);
    	for (int i = G[u], v; ~i; i = e[i].nxt)
    		if ((v = e[i].v) != son[u] && v != par[0][u]) dfs2(v, v);
    }
    
    int base[maxn]; // 维护log2(n)的下取整
    
    int query(int u, int k) {
    	if (!k) return u; // 第一种情况:跳的高度为0。直接返回u
    	if (k >= dep[u]) return 0; // 提前判断是否会跳出树,返回0
    	u = par[base[k]][u], k ^= 1<<base[k]; // 否则先跳2^base[k],剩余的部分根据长剖的性质直接跳
    	return k <= dep[u]-dep[top[u]] ? chain[top[u]][dep[u]-dep[top[u]]-k] : up[top[u]][k-dep[u]+dep[top[u]]]; // 根据跳完是否在这条长链上分类讨论
    }
    
    int main() {
    	n = read(); clear();
    	for (int i = 1, u, v; i < n; i++) u = read(), v = read(), adde(u, v), adde(v, u);
    	dfs1(1), dfs2(1, 1);
    	rep(i, 1, n) if (top[i] == i) { // 处理出来长链和向上跳的k级
    		int len = mxd[i] - dep[i]; // 长链的长度为len
    		for (int u = i, j = 0; j <= len; u = son[u], j++) chain[i].push_back(u);
    		for (int u = i, j = 0; j <= len; u = par[0][u], j++) up[i].push_back(u);
    	}
    	rep(i, 2, n) base[i] = base[i>>1] + 1;
    	for (int ans = 0, Q = read(), u, k; Q; Q--) u = ans ^ read(), k = ans ^ read(), printf("%d
    ", ans = query(u, k));
    	return 0;
    }
    
  • 相关阅读:
    oracle 之索引,同义词 ,关键词,视图 ,存储过程,函数,触发器
    基于TCP协议的socket通信
    支付宝支付 -即时到帐
    Hibernate延迟加载机制
    shiro 简单的身份验证 案例
    linux 试题
    程序员面试题精选100题(16)-O(logn)求Fibonacci数列[算法]
    九度OJ 1362 左旋转字符串(Move!Move!!Move!!!)【算法】
    九度OJ 1366 栈的压入、弹出序列 【数据结构】
    九度OJ 1387 斐波那契数列
  • 原文地址:https://www.cnblogs.com/ac-evil/p/12158313.html
Copyright © 2011-2022 走看看