zoukankan      html  css  js  c++  java
  • SPOJ-QTREE4 Query on a tree IV

    Description

    给出一棵边带权((c))的节点数量为 (n) 的树,初始树上所有节点都是白色。有两种操作:

    • C x,改变节点 (x) 的颜色,即白变黑,黑变白。

    • A,询问树中最远的两个白色节点的距离,这两个白色节点可以重合(此时距离为 (0))。

    (q) 次操作,输出所有查询的答案。

    Hint

    • (1le n, qle 10^5)
    • (0le |c|le 10^3)

    Solution

    此题使用轻重链剖分真的麻烦

    先树剖,然后根据每一个重链,建出一棵线段树(最后建出的是线段树森而非一棵大线段树,动态开点实现)。设 (root(x)) 为结点 (x) 对应线段树的根。非链顶结点的 (root) 无意义。

    线段树上的每个结点维护 (3) 个字段:

    • (lx(x)) 结点 (x) 代表的链上一段区间的 左端点(深度小的)可以到达 以链顶为根的子树 中最远的白点的距离。
    • (rx(x)) 结点 (x) 代表的链上一段区间的 右端点(深度大的)可以到达 以链顶为根的子树 中最远的白点的距离。
    • (mx(x)) 结点 (x) 代表满足 LCA 在当前结点区间中的所有白点对 中最大的距离。

    那么我们可以这样设计我们的 pushup 函数,注意此处的 (dep) 带边权。

    #define dis(x) dep[pos[x]] // dis(i) 表示区间中位置 i 所对应结点的深度 
    void pushup(int x, int l, int r) { // x 为线段树上当前结点,对应区间为 [l, r] 
    	lx[x] = max(lx[lc[x]], lx[rc[x]] + dis(mid + 1) - dis(l));
    	// 可以从左儿子转移而来,也可以从右儿子跨越中间而来。 
    	rx[x] = max(rx[rc[x]], rx[lc[x]] + dis(r) - dis(mid));
    	// 可以从右儿子转移而来,也可以从左儿子跨越中间而来。
    	mx[x] = max(max(mx[lc[x]], mx[rc[x]]), lx[rc[x]] + rx[lc[x]] + dis(mid + 1) - dis(mid));
    	// 可以从儿子结点转移而来,或者计算出跨越中心情况的答案。 
        // dis 的差值实质上是边权
    }
    #undef dis
    

    答案即为 (max{mx})

    如何处理叶结点的值?显然不能爆算子树中所有结点的距离,因为深度总和会达到 (O(n^2)) 级别。

    对于一个 线段树上 的叶子结点 (x),其对应的 原树 结点为 (u)。对于其 父结点或重儿子,由于在同一条链上 ,无需过多考虑。(u) 的所有轻儿子,显然它们一定是其所在链的链顶。

    对于其中一个轻儿子 (v),易知 (lx(root(v))) 子树 (v) 中向下延伸的最长合法路径。那么加上当前路径就是 一条“LCA 位于区间 ([dfn(u), dfn(u)])”的合法路径。((dfn(x)) 表示原树上结点 (x) 的 dfs 序)。

    (d_1) 为一端为 (u)最长向下 路径长,(d_2)次长向下 路径长。不存在设为 (-infty)

    不难得出,(lx(x), rx(x)) 的值就是 (max(d_1, 0))(mx(x)) 的值可以由最长、次长两条路径拼成。无需考虑路径会不会重合,因为来自不同的子树。由于白点自身可以作为路径的端点,(mx(x)) 的值需要分类讨论。

    • 白点:(mx(x) = max(d_1, d_1 + d_2, 0))
    • 黑点:(mx(x) = max(d_1 + d_2, 0))

    对于最大值、次大值的维护,可以使用堆。在叶结点遍历轻儿子时顺便将堆更新。求次大值时只需将堆顶弹出,取值后重新塞回即可。

    答案即为 (maxlimits_{xin ext{tops}} { mx(root(x))}),同样可以用一个全局堆维护。

    建树操作参考代码:

    void build(int& x, int l, int r) {
    	if (!x) x = ++total; // 动态开点
    	if (l == r) {
    		int u = pos[l];
    		getEdge(u, v) if (v->to != fa[u] && v->to != wson[u])
    			pt[u].insert(lx[root[v->to]] + dep[v->to] - dep[u]);
    		// pt 为堆
    		int d1 = pt[u].top(); // 最大
    		pt[u].erase(d1);
    		int d2 = pt[u].top(); // 次大
    		pt[u].insert(d1);
    		
    		lx[x] = rx[x] = max(d1, 0);
    		mx[x] = max(d1, max(d1 + d2, 0));
    		return;
    	}
    	build(lc[x], l, mid);
    	build(rc[x], mid + 1, r);
    	pushup(x, l, r);
    }
    

    考虑修改操作。一个修改可能 会影响到其祖先的答案,于是我们需要一直向上跳。

    设当前跳到的位置为 (x),上次位置的链顶为 (y)

    首先在 链顶父亲 结点的堆中删去 当前链顶的贡献,下一次跳在重新将 更新过的值插入

    那么在线段树上修改时,将堆中 (y) 方向轻儿子的贡献 重新插入 (x) 的堆中,像 build 一样维护即可。

    同时别忘了更新全局堆。

    下面给出修改的代码:

    void update(int x, int l, int r, int u, int v) {
    	if (l == r) {
    		if (u != v)
    			pt[u].insert(lx[root[v]] + dep[v] - dep[u]);
    		
    		int d1 = pt[u].top();
    		pt[u].erase(d1);
    		int d2 = pt[u].top();
    		pt[u].insert(d1);
    		
    		if (color[u]) {
    			lx[x] = rx[x] = d1;
    			mx[x] = d1 + d2;
    		} else {
    			lx[x] = rx[x] = max(d1, 0);
    			mx[x] = max(d1, max(d1 + d2, 0));
    		}
    		return;
    	}
    	if (dfn[u] <= mid) update(lc[x], l, mid, u, v);
    	else update(rc[x], mid + 1, r, u, v);
    	pushup(x, l, r);
    }
    
    void change(int x) {
    	color[x] ^= 1;
    	if (color[x] == 0) ++white;
    	else --white;
    	
    	for (int y = x; x; x = fa[x]) {
    		int top = wtop[x];
    		all.erase(mx[root[top]]);
    		
    		if (fa[top]) pt[fa[top]].erase(lx[root[top]] + dep[top] - dep[fa[top]]);
    		update(root[top], dfn[top], dfn[top] + len[top] - 1, x, y);
    		
    		all.insert(mx[root[top]]);
    		y = x = top;
    	}
    }
    

    那么算法基本算是完成了。

    但实现非常复杂,细节多(上面代码)。

    不过实测表现不差,原因是树剖、线段树的 (log) 都跑不满。

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

    参考代码:https://vjudge.net/solution/26745510/8T7tgRJPSKwBPUqVL9r2

  • 相关阅读:
    【2020-08-30】盼头这事,还是得有一点好
    【一句日历】2020年9月
    【2020-08-29】边走边想吧,少年
    【2020-08-28】欲望与能力的矛盾假象
    【2020-08-27】人生十三信条
    【2020-08-26】日复一日,年复一年
    【2020-08-25】今天七夕,男人有话要说
    【2020-08-24】处处较真,其实就是自己虚荣
    【2020-08-23】人生十三信条
    【2020-08-22】人生十三信条
  • 原文地址:https://www.cnblogs.com/-Wallace-/p/spojqtree4.html
Copyright © 2011-2022 走看看