洛谷·【模板】点分树 | 震波【including 点分树

初见安~这里是传送门:洛谷P6329 【模板】点分树 | 震波

洛谷·【模板】点分树 | 震波【including 点分树_第1张图片

一、点分树

其实你会点分治的话,点分树就是把点分治时的重心提出来重新连城一棵树。

比如当前点是u,求出子树v的重心root后将root与u连边。如此递归下去,就是一棵点分树。

有什么用呢?因为重构了树的结构,并且保证了深度不超过logn,所以可以把一些极其暴力的操作的复杂度变正确。

比如震波这个题。

二、题解

题意很显然:单点修改和求距某点距离在K以内的所有点的点权和

借用点分树的性质,我们建树过后考虑如何维护这个暴力(不容易想到 算是点分树自带的套路?)。另f_{u,i}表示点u在点分树上的子树内距离在i以内的点的点权和。注意,这里可能对f_{u,i}产生贡献的点是u在点分树上的子树内的点,但是距离是原树的距离。我们先不考虑修改,如果是询问的话,显然可以用f_{u,i}统计一部分的点。但是因为是重构过的,所以有可能有些点还在上面,所以我们要挨着往上走,也就是从u暴力跳到整个点分树的树根去。并且点分树上点在原树上不一定相邻,所以对于当前某个父亲x,所求为距离点u距离为K以内的点的点权和,x的贡献为f_{x,K-dis(u,x)}

到这里就会有个问题:是否存在这样一个被统计到的点v,fa[u]是点分树上u的父亲,路径u->fa[u]->v有重复经过某条边,但依旧在K以内导致v被计算了两次?那必然存在。考虑如何容斥掉这种情况。其实这种重复的情况出现只有当原树上u、v、fa[u]是这样的祖先关系时才会出现(大概?)也就是,v是u的祖先,fa[u]又是v的祖先。这样的话如果会重复遍历,那么点分树上v一定在u的子树内。换言之,我们会重复遍历到v完全是因为在u的时候f_{u,K}计算过了一次,在f_{fa[u],K-dis(u,fa[u])}又计算了一次。所以减去u子树内对fa[u]的贡献即可。换言之我们再搞一个g_{u,i}表示点分树上u的子树内的点到fa[u]距离为i的点的点权和。容斥掉即可。

修改也很容易,因为每个点的点权会影响到的就只有它的所有祖先,是log级别的。暴力跳上去就好。

但是这个题因为求的是前K个,所以需要一个前缀和。这个就用树状数组了,总的时间复杂度多一个log,为nlog^2n。

以及,因为n个点,每个点都有一个树状数组,空间不可能开n^2,所以考虑实际距离应该在size[u]以内,卡着子树大小size[u]开就好。空间复杂度也是nlogn的。(可是我卡着开就全RE了不知道为什么……)

上代码:

#include
#include
#include
#include
#include
#include
#include
#define maxn 100005
using namespace std;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

struct edge {int to, nxt;} e[maxn << 1];
int head[maxn], k = 0;
void add(int u, int v) {e[k] = {v, head[u]}; head[u] = k++;}

int n, m, a[maxn];
int dep[maxn], st[maxn << 1][20], num[maxn], tot = 0;
int get_min(int a, int b) {return dep[a] < dep[b]? a : b;}
void dfs(int u, int fa) {
	st[++tot][0] = u; num[u] = tot;
	for(int i = head[u], v; ~i; i = e[i].nxt) {
		v = e[i].to; if(v == fa) continue;
		dep[v] = dep[u] + 1; dfs(v, u); st[++tot][0] = u;
	}
}

int root, size[maxn], part[maxn], max_part, all_part = 0;
bool vis[maxn];
void get_root(int u, int fa) {
	size[u] = 1; part[u] = 0; all_part++;
	for(int i = head[u], v; ~i; i = e[i].nxt) {
		v = e[i].to; if(v == fa || vis[v]) continue;
		get_root(v, u); size[u] += size[v];
		part[u] = max(part[u], size[v]);
	}
	part[u] = max(part[u], max_part - size[u]);
	if(part[u] < part[root]) root = u;
}

int sz[maxn], fa[maxn];
vector t[maxn][2];
void slv(int u) {
	vis[u] = true; sz[u] = all_part + 1;//allpart是求重心的时候算的整个子树大小,但是要+1才行,按理说不该有这么远的点……?QwQ
	t[u][0].resize(sz[u] + 1); t[u][1].resize(sz[u] + 1);//这里+1是因为算上0位置
	for(int i = head[u], v; ~i; i = e[i].nxt) {
		v = e[i].to; if(vis[v]) continue;
		max_part = size[v]; all_part = root = 0; get_root(v, u);
		fa[root] = u; slv(root);
	} 
}

int get_dis(int u, int v) {
	register int x = num[u], y = num[v]; if(x > y) swap(x, y);
	register int len = (int)log2(y - x + 1), lca = get_min(st[x][len], st[y - (1 << len) + 1][len]);
	return dep[u] + dep[v] - (dep[lca] << 1);
}
//两行树状数组
void ist(int u, int op, int x, int w) {x++; for(; x <= sz[u]; x += (x & -x)) t[u][op][x] += w;}
int ask(int u, int op, int x) {x++; int res = 0; for(x = min(x, sz[u]); x; x -= (x & -x)) res += t[u][op][x]; return res;}
void update(int u, int val) {//楼上树状数组,求答案的时候一定记得上界取min
	for(int i = u; i; i = fa[i]) ist(i, 0, get_dis(u, i), val);
	for(int i = u; fa[i]; i = fa[i]) ist(i, 1, get_dis(u, fa[i]), val);
}

signed main() {
	n = read(), m = read(); memset(head, -1, sizeof head);
	for(int i = 1; i <= n; i++) a[i] = read();
	for(int i = 1, u, v; i < n; i++) u = read(), v = read(), add(u, v), add(v, u);
	
	dfs(1, 0);
	root = 0, max_part = part[0] = n; get_root(1, 0);
	slv(root);//建点分树
	
	dep[0] = n + n;
	for(int i = 1; (1 << i) <= tot; i++) for(int j = 1; j + (1 << i) <= tot; j++) 
		st[j][i] = get_min(st[j][i - 1], st[j + (1 << i - 1)][i - 1]);//O1求LCA,st表

	for(int i = 1; i <= n; i++) update(i, a[i]);//更新值
	int ans = 0;
	while(m--) {
		register int op = read(), x = read(), y = read();
		x ^= ans, y ^= ans;
		if(!op) {
			ans = ask(x, 0, y);
			for(int i = x; fa[i]; i = fa[i]) {
				register int d = get_dis(x, fa[i]);//往上跳。因为点分树距离不单调所以都要求
				if(y >= d) ans += ask(fa[i], 0, y - d) - ask(i, 1, y - d);
			}
			printf("%d\n", ans);
		} else update(x, y - a[x]), a[x] = y;
	}
	return 0;
}

迎评:)
——End——

 

 

你可能感兴趣的:(树型结构,点分树)