毛毛虫剖分

毛毛虫剖分

其实就是一种重链剖分的重标号方法。舍弃较为没用的部分,让它实现更加强大的功能!

学了这个其实可以更加彻底地理解重链剖分的本质。

小 W 有一棵 \(n\) 个结点的树,树上的每一条边可能是轻边或者重边。接下来你需要对树进行 \(m\) 次操作,在所有操作开始前,树上所有边都是轻边。操作有以下两种:

  1. 给定两个点 \(a\)\(b\),首先对于 \(a\)\(b\) 路径上的所有点 \(x\)(包含 \(a\)\(b\)),你要将与 \(x\) 相连的所有边变为轻边。然后再将 \(a\)\(b\) 路径上包含的所有边变为重边。
  2. 给定两个点 \(a\)\(b\),你需要计算当前 \(a\)\(b\) 的路径上一共包含多少条重边。

\(1\le n,m\le 10^5,1\le T\le 3\)

我们考虑重链剖分的实质,它给我们提供了两条信息:

  • 每一条重链上节点的编号都是连续的。
  • 每棵子树内的 dfs 序都是成段的。

发现一般的重链剖分题目里我们都用不到第二条性质,这道题也是这样。考虑魔改出一种符合我们需要的数据结构,它要(尽可能)支持:

  • 每一条重链上节点的编号都是连续的。
  • 重链上每个节点的轻儿子编号都是连续的。
  • 所有与重链节点相连的点编号都是连续的。

考虑这样一种重标号方式:

  1. 常规重剖,找出所有的重链,把以根节点为链首的重链加入队列。
  2. 取出队首,依次对该重链上的节点进行标号,然后对重链上每个节点的轻儿子进行连续地标号,将这些轻儿子加入队列。同时预处理出来两个数组:\(\rm mx\) 表示重链中该节点及以上的点所连点的最大值,\(\rm mn\) 表示重链中该节点及以下的点所连点的最小值。这个是可以顺序预处理的。不断重复以上过程。

不难发现这样可以达成我们的目的,只是有一个小问题:部分重链的链首和链上的节点编号不一定是连续的。不过这个不是问题,就一个节点,在修改和查询的时候特殊处理就行。具体看代码。https://loj.ac/s/1840921

这样的标号方式支持我们对所有轻儿子和重链分别进行不同的操作。严格强于普通的重链剖分。唯一的缺点就是因为链首标号不连续会徒增一些常数,导致这题 LOJ 上才能 0.998s 极限卡过去。不太会卡常,有无大佬教教。

  • [GLR-3 F] 谷雨

给定一棵含有 \(n\) 个结点的树,点有点权。现进行 \(q\) 次操作,每次操作形如:

  1. 修改操作 给定 \(u,v,k\),对于所有在路径 \(u\rightsquigarrow v\) 上,或者存在一个邻接点在路径 \(u\rightsquigarrow v\) 上的结点 \(w\),将其点权变为 \(k\)
  2. 询问操作 给定 \(u,v\),从 \(u\) 出发沿树边走向 \(v\),走到结点 \(w\) 时:
    • \(w\) 的点权加入队列 \(S\)
    • 按标号从小到大枚举 \(w\) 的邻接点 \(x\),若 \(x\) 不在路径上,将 \(x\) 的点权加入队列 \(S\)
    • 最后,走向路径上的下一个点。求出 \(S\) 的最大可空子段和。

\(1\le n, q\le 10^5\)

让我们更加深刻地理解一下毛毛虫剖分。它是一种重标号方式,这个方式是根据实际需求决定的,所以适用于 [NOI2021] 轻重边 的标号方式不一定适合其它题目,在毛毛虫剖分的基础框架下,我们还是要针对性地设计标号方式。

考虑这道题目我们需要得到的是重链上节点和其轻儿子标号相邻的性质。轻重边 的标号方式为我们提供了 虫足和虫身相分离,且虫足、虫身标号各自连续的性质,所以我们可以分别对它们进行不同的操作。

这道题略有不同,我们发现虫足和虫身的操作和查询都相同,所以用不到这种分离式的标号方法,舍弃掉这个性质,换一种标号方式,可以为我们提供更有用的性质。而最大子段和的经典线段树做法需要节点的拼接,所以我们要维护出来毛毛虫对应的顺序序列。

考虑这样一种标号方式:

  • 将根节点编号后,把这条重链加入队列。
  • 取出队首 \(u\),依次遍历 \(u\) 所在重链上节点 \(v\),为 \(v\) 标号后顺序遍历 \(v\) 的轻儿子,为轻儿子标号,加入队列。
  • 重复以上过程。

不难发现这样的话我们把重链上节点和它的轻儿子绑到了一块,现在可以直接做 \(1\) 操作了。线段树维护对应序列的最大子段和,配上树剖是 \(\mathcal O(q\log^2n)\)

此时还有一个问题:\(2\) 操作中,两个节点分别向上跳,要在 LCA 处拼接两个序列,但这样的话得有一个序列翻转才能正常拼接。普通的线段树 tag 似乎难以实现。

于是我们保留上述标号的同时,再新开一组标号,过程就是上面那个方法直接倒过来:

  • 将根节点编号后,把这条重链加入队列。
  • 取出队首 \(u\),顺序遍历 \(u\) 所在重链上节点 \(v\),先倒序遍历 \(v\) 的轻儿子,为轻儿子标号后加入队列,再为 \(v\) 标号。
  • 重复以上过程。

开两颗线段树分别维护两种标号对应的序列,查询的时候把两个对应的序列拼起来就行。\(\mathcal O(q\log^2n)\)。这代码是给人写的?不写。流泪。最讨厌的一集。

upd: 贺完 std 了捏。

// 拥向光如抱薪赴火, 妄想着永不分离的梦 
#include <bits/stdc++.h>
#define pb emplace_back
#define fir first
#define sec second
#define IL inline
using i64 = long long;
using pii = std::pair<int, int>;

IL int read() {
	int s = 0;
	bool f = false;
	char c = getchar();
	for(;c < '0'||c > '9';c = getchar())
		if(c == '-')
			f = true;
	for(;c >= '0'&&c <= '9';c = getchar())
		s = (s << 1) + (s << 3) + (c ^ '0');
	return f ? -s : s;
}
IL void write(i64 x) {
	if(x > 9)
		write(x / 10);
	return putchar(x % 10 + '0'), void();
}
IL i64 min(i64 x, i64 y) { return x < y ? x : y; }
IL i64 max(i64 x, i64 y) { return x > y ? x : y; }
IL void chkmin(int& x, int y) { if(y < x) x = y; return ; }
IL void chkmax(int& x, int y) { if(y > x) x = y; return ; }

const int maxn = 1e5 + 5, INF = 1e9 + 5;
int n, q, Q[maxn], hd, tl, mxv[maxn][2], mnv[maxn][2];
int f[maxn], sz[maxn], son[maxn], d[maxn], top[maxn], rk[maxn][2], dfn[maxn][2], dfc[2], a[maxn], spos[maxn][2];
std::vector<int> G[maxn], V[maxn];

struct node {
	i64 sum, lmx, rmx, ans;
	node() {
		sum = lmx = rmx = ans = 0;
	}
	node(i64 sum, i64 lmx, i64 rmx, i64 ans) : sum(sum), lmx(lmx), rmx(rmx), ans(ans) {}
	IL void print() {
		return printf("test: %lld %lld %lld %lld\n", sum, lmx, rmx, ans), void();
	}
	IL node rev() const { return node(sum, rmx, lmx, ans); }
	IL node operator + (const node& rhs) const {
		return node(sum + rhs.sum, max(lmx, sum + rhs.lmx), max(rhs.rmx, rhs.sum + rmx), max(max(ans, rhs.ans), rmx + rhs.lmx));
	}
	IL node operator += (const node& rhs) {
		return *this = rhs + *this;
	}
	IL node operator *= (const node& rhs) {
		return *this = *this + rhs;
	}
};

struct SMT {
	int ls[maxn << 2], rs[maxn << 2], len[maxn << 2], tag[maxn << 2];
	node res[maxn << 2];
	IL void pushup(int i) {
		return res[i] = res[i << 1] + res[i << 1 | 1], void();
	}
	IL void pushtag(int i, int x) {
		return tag[i] = x, res[i].sum = 1ll * x * len[i], res[i].lmx = res[i].rmx = res[i].ans = max(0ll, res[i].sum), void(); 
	}
	IL void pushdown(int i) {
		if(tag[i] != INF)
			pushtag(i << 1, tag[i]), pushtag(i << 1 | 1, tag[i]), tag[i] = INF;
		return ;
	}
	IL void build(int i, int l, int r, const int& id) {
		ls[i] = l, rs[i] = r, len[i] = r - l + 1, tag[i] = INF;
		if(l == r) {
			const int t = rk[l][id]; const int val = max(0, a[t]);
			return res[i] = node(a[t], val, val, val), void();
		}
		int mid = (l + r) >> 1;
		return build(i << 1, l, mid, id), build(i << 1 | 1, mid + 1, r, id), pushup(i);
	}
	IL void assign(int i, int l, int r, int x) {
		if(l > r)
			return ;
		if(ls[i] >= l&&rs[i] <= r)
			return pushtag(i, x);
		int mid = (ls[i] + rs[i]) >> 1; pushdown(i);
		if(l <= mid)
			assign(i << 1, l, r, x);
		if(r > mid)
			assign(i << 1 | 1, l, r, x);
		return pushup(i);
	}
	IL node query(int i, int l, int r) {
		if(l > r)
			return node(0, 0, 0, 0);
		if(ls[i] > r||rs[i] < l)
			return node(0, 0, 0, 0);
		if(ls[i] >= l&&rs[i] <= r)
			return res[i];
		int mid = (ls[i] + rs[i]) >> 1; pushdown(i);
		if(l > mid)
			return query(i << 1 | 1, l, r);
		else if(r <= mid)
			return query(i << 1, l, r);
		else
			return query(i << 1, l, r) + query(i << 1 | 1, l, r);
	}
} smt[2];

IL void dfs1(int u) {
	sz[u] = 1; d[u] = d[f[u]] + 1;
	for(auto& v : G[u]) {
		dfs1(v); sz[u] += sz[v];
		if(sz[son[u]] <= sz[v])
			son[u] = v;
	}
	return ;
}

IL void dfs2(int u) {
	if(!top[u])
		top[u] = u;
	V[top[u]].pb(u);
	if(!son[u])
		return ;
	top[son[u]] = top[u]; dfs2(son[u]);
	for(auto& v : G[u])
		if(v != son[u])
			dfs2(v);
	return ;
}

IL int LCA(int u, int v) {
	while(top[u] != top[v]) {
		if(d[top[u]] < d[top[v]])
			std::swap(u, v);
		u = f[top[u]];
	}
	return d[u] < d[v] ? u : v;
}

IL void assign(int u, int v, const int& k, const int& id) {
	while(top[u] != top[v]) {
		if(d[top[u]] < d[top[v]])
			std::swap(u, v);
		const int t = top[u];
		smt[id].assign(1, mnv[t][id], mxv[u][id], k);
		if(son[u])
			smt[id].assign(1, dfn[son[u]][id], dfn[son[u]][id], k);
		smt[id].assign(1, dfn[t][id], dfn[t][id], k); u = f[t];
	}
	if(d[u] > d[v])
		std::swap(u, v);
	smt[id].assign(1, mnv[u][id], mxv[v][id], k);
	if(son[v])
		smt[id].assign(1, dfn[son[v]][id], dfn[son[v]][id], k);
	if(u == top[u])
		smt[id].assign(1, dfn[u][id], dfn[u][id], k);
	if(f[u])
		smt[id].assign(1, dfn[f[u]][id], dfn[f[u]][id], k);
	return ;
}

IL void append(node& res, int u, int p, const int& id) {
	const int l = mnv[u][id], r = mxv[u][id];
	if(dfn[p][id] < spos[u][id]) {
		res += smt[id].query(1, spos[u][id] + 1, r);
		res += smt[id].query(1, dfn[son[u]][id], dfn[son[u]][id]);
		res += smt[id].query(1, max(l, dfn[p][id] + 1), spos[u][id]);
		res += smt[id].query(1, l, dfn[p][id] - 1);
	}
	else {
		res += smt[id].query(1, max(dfn[p][id] + 1, l), r);
		res += smt[id].query(1, max(spos[u][id] + 1, l), dfn[p][id] - 1);
		if(son[u])
			res += smt[id].query(1, dfn[son[u]][id], dfn[son[u]][id]);
		res += smt[id].query(1, l, min(spos[u][id], dfn[p][id] - 1));
	}
	return ;
}

IL std::pair<node, int> climb(int u, int tar, const int& id) {
	node res = node(0, 0, 0, 0); int p = 0;
	while(top[u] != top[tar]) {
		if(u != top[u]) {
			append(res, u, p, id);
			res += smt[id].query(1, mxv[top[u]][id] + 1, mnv[u][id] - 1);
			u = top[u];
			if(!id)
				res += smt[id].query(1, mnv[u][id], mxv[u][id]), res += smt[id].query(1, dfn[u][id], dfn[u][id]);
			else
				res += smt[id].query(1, dfn[u][id], dfn[u][id]), res += smt[id].query(1, mnv[u][id], mxv[u][id]);
		}
		else {
			if(!id)
				append(res, u, p, id), res += smt[id].query(1, dfn[u][id], dfn[u][id]);
			else
				res += smt[id].query(1, dfn[u][id], dfn[u][id]), append(res, u, p, id);
		}
		u = f[p = u];
	}
	if(u != tar)
		append(res, u, p, id), res += smt[id].query(1, mnv[p = son[tar]][id], mnv[u][id] - 1);
	return {res, p};
}

IL i64 query(int u, int v) {
	int t = LCA(u, v); auto su = climb(u, t, 1), sv = climb(v, t, 0); node res = su.fir.rev();
//	node cur = su.fir; node ncur = sv.fir;
//	cur.print(); ncur.print();
	res *= smt[0].query(1, dfn[t][0], dfn[t][0]);
	if(f[t])
		res *= smt[0].query(1, dfn[f[t]][0], dfn[f[t]][0]);
	std::vector<pii> tmp;
	if(su.sec&&su.sec != son[t])
		tmp.pb(dfn[su.sec][0], 0);
	if(sv.sec&&sv.sec != son[t])
		tmp.pb(dfn[sv.sec][0], 0);
	if(su.sec != son[t]&&sv.sec != son[t])
		tmp.pb(spos[t][0], 2);
	tmp.pb(mxv[t][0], 1); std::sort(tmp.begin(), tmp.end());
	int lst = mnv[t][0] + (t != top[t]);
	for(auto& p : tmp) {
		int l = p.fir, r = p.sec;
		res *= smt[0].query(1, lst, l - !r);
		if(l == spos[t][0]&&r == 2)
			res = res + smt[0].query(1, dfn[son[t]][0], dfn[son[t]][0]);
		lst = l + 1;
	}
	res *= sv.fir;
	return res.ans;
}

int main() {
	read(); n = read();
	for(int i = 1;i <= n;++ i)
		a[i] = read();
	for(int i = 2;i <= n;++ i)
		f[i] = read(), G[f[i]].pb(i);
	dfs1(1); dfs2(1);
	Q[hd = tl] = 1; dfn[1][0] = dfn[1][1] = rk[1][0] = rk[1][1] = dfc[0] = dfc[1] = 1;
	while(hd <= tl) {
		int u = Q[hd ++], len = V[u].size();
		for(int i = 0;i < len;++ i) {
			int x = V[u][i];
			mnv[x][0] = dfc[0] + 1;
			if(!dfn[x][0])
				dfn[x][0] = ++ dfc[0], rk[dfc[0]][0] = x;
			for(auto& v : G[x]) {
				if(v == son[x])
					spos[x][0] = dfc[0];
				else
					Q[++ tl] = v, dfn[v][0] = ++ dfc[0], rk[dfc[0]][0] = v;
			}
			mxv[x][0] = dfc[0];
		}
		for(int i = 0;i < len;++ i) {
			int x = V[u][i];
			mnv[x][1] = dfc[1] + 1;
			for(int j = G[x].size() - 1;~ j;-- j) {
				int v = G[x][j];
				if(v == son[x])
					spos[x][1] = dfc[1];
				else
					dfn[v][1] = ++ dfc[1], rk[dfc[1]][1] = v;
			}
			if(!dfn[x][1])
				dfn[x][1] = ++ dfc[1], rk[dfc[1]][1] = x;
			mxv[x][1] = dfc[1];
		}
	}
	smt[0].build(1, 1, n, 0); smt[1].build(1, 1, n, 1);
	q = read();
	while(q --) {
		int op = read(), x = read(), y = read(), z;
		if(op == 0)
			z = read(), assign(x, y, z, 0), assign(x, y, z, 1);
		else
			write(query(x, y)), puts("");
	}
	return 0;
}
posted @ 2023-07-24 21:00  ImALAS  阅读(61)  评论(0编辑  收藏  举报