边分树学习笔记

暴力写挂了呜呜呜

边分治

类比点分治,这里枚举中心边,把树拆成两个大小相接近的部分。每次递归下去分治做就好了。容易发现这个东西吊打点分治,因为每次只会分成两个大小相近的部分,所以会多出很多优美的性质来。

但是非常不幸的,在菊花图上这个的复杂度是错的。

但是我们有两倍常数的非常简单的解决办法:把这棵树三度化!根据某个神秘定理,在二叉树上做上面的算法复杂度正确。

但他可不是乱打的啊!要在多叉树转二叉树的过程中保证任意两点距离相同!所以我们需要增加虚点和0权边。

大概就是这样吧,意会一下(((

如果放上建树的代码的话,就是这样:

code
void DFS1(int u, int f) {
	int lst = u, flag = 0;
	for(int i = g1.h[u]; ~i; i = g1.ne[i]) {
		int j = g1.e[i];
		if(j == f) continue;
		if(flag) {
			++now;
			g2.add(now, lst, 0);
			g2.add(lst, now, 0);
			lst = now;
		} 
		flag = 1;
		g2.add(lst, j, g1.w[i]);
		g2.add(j, lst, g1.w[i]);
		dep1[j] = dep1[u] + g1.w[i];
		DFS1(j, u);
	}
}

除了第一个节点以外,每一个子节点都要新建一个虚点。感觉不难理解。

记录边分治的过程

e,就是对每个点,记录一下每次在边分治的时候它进入了哪个子树,知道是左子树还是右子树就行了。这样的序列长度是 log 的,而且每个序列可以唯一对应一个点。

但是只有这个显然用处不大对吧,所以我们还需要顺便在子树的方向存下点到分治中心的距离。为了方便,分治中心边的长度随便分给一边就行了。

这里要额外注意一个细节,这里新建每个点的时候要在上次记录距离的方向新建。(也就是说,上一次确定方向并更新信息,这一次在上次确定的方向上开新点。)

可以不这样写,但是会使得每个点都要建一个虚空根节点,很没必要。

code
void DFS3(int u, int f, ll dis, ll dep, int type) {
		//更新每个点的边分树信息
		if(u <= n) {
			if(!lst[u]) {
				tr[u] = ++tot;
			}
			else {
				ch[lst[u]][lstp[u]] = ++tot;
			}
			sz[tot][type]++;
			sum[tot][type] = dep + dis;
			lst[u] = tot;
			lstp[u] = type;
		} 
		for(int i = t2.h[u]; ~i; i = t2.ne[i]) {
			int j = t2.e[i];
			if(j == f || vis[i]) continue;
			DFS3(j, u, dis, dep + t2.w[i], type);
		}
	}

(这个东西有什么用?)

hmmmm,好问题。这个东西本身没有什么用,但是注意到这个东西和线段树的一条单链结构很像,所以这个东西和线段树一样是可以合并的。把所有这些链合并起来就构成原树的边分树。

但是比这更重要的是合并的过程,线段树合并具有优美的性质:两个线段树的每一个“分叉”都会被枚举到。这里的“分叉”指的是对应节点往不同方向延申的两个子树。而这恰恰是边分治需要的——合并左右两个分治子树的信息。

[CTSC2018]暴力写挂解题报告

建议更改题目名字为【模板】边分树合并

把题目要求的式子变成 12(disx,y+depx+depy)depLCAx,y ,边分治后容易得到一个平凡的虚树做法,可以获得95分,精细实现似乎可以做到 Θ(nlogn) 就能通过。

正解直接利用边分树合并的性质,对第一棵树边分树后在第二棵树上合并,每个节点 t 合并起来的两棵边分树就保证了 depLCAx,y=t ,在合并的过程统计不同方向子树的 12(disx,a+disy,b+wa,b+depx+depy)depLCAx,y 就可以了。

CF757G-Can Bash Save the Day? 解题报告

第一眼:这不是我们HNOI的开店吗?下次记得标记出处!

FST

笑容逐渐消失。。。

我们需要更好的做法。

换个方向,对于区间的限制,我们除了在点分树上做区间查询以外,还可以把问题离线下来,对于每个右端点维护所有前缀的答案。然后差分一下。

但是它在线啊!所以我们需要可持久化。

但是它带修啊!这个做法凉了吗?

注意到其实修改只有交换相邻项,所以我们可以只用修改一个版本的数据结构,看起来这个对了。

我们仿照上例建立每个点的边分树单链。然后把它们可持久化起来。如果写成可持久化合并的形式就可以减少很多麻烦。

每个点要记录特定方向上的路径条数和路径长度总和。这样计算答案是容易的。

修改其实也没有想象中的麻烦,直接在第 x1 个版本上可持久化地合并上 ax+1 对应的那棵树就好了。

代码出人意料的好写。

code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int P = 998244353;
const int N = 4e5+5;
template<typename T>inline void read(T &a)
{
	a = 0;
	int f = 1;
	char c = getchar();
	while(!isdigit(c))
	{
		if(c == '-')	f = -1;
		c = getchar();
	}
	while(isdigit(c))
		a = a * 10 + c - 48,c = getchar();
	a *= f;
}

template<typename T,typename ...L> inline void read(T &a,L &...l)
{
	read(a),read(l...);
}
//我们的边分树实在是太厉害了! 
int tot, ch[N * 30][2], tr[N], root[N];//每个点的边分树,根的边分树 
ll sum[N * 30][2], sz[N * 30][2], lstans;
int n, q, aa[N], u, v, w, t, a, b, c; 

struct Tree {
	int h[N], e[N << 1], ne[N << 1], w[N << 1], idx;
	Tree() {
		memset(h, -1, sizeof h);
	}
	void add(int a, int b, int c) {
		e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
	}
}t1, t2;

namespace conquer {
	int S, lst[N], lstp[N];//每个点的上一次位置和拐弯的地方 
	int nn, vis[N << 1], sze[N], root, res;
	void DFS1(int u, int f) {
		int lst = u, flag = 0;
		for(int i = t1.h[u]; ~i; i = t1.ne[i]) {
			int j = t1.e[i];
			if(j == f) continue; 
			if(flag) {
				S++;
				t2.add(lst, S, 0), t2.add(S, lst, 0), lst = S; 
			}
			flag = 1;
			t2.add(lst, j, t1.w[i]), t2.add(j, lst, t1.w[i]);
			DFS1(j, u);
		}
	}
	
	void DFS2(int u, int f) {//求分治中心 
		sze[u] = 1;
		for(int i = t2.h[u]; ~i; i = t2.ne[i]) {
			int j = t2.e[i];
			if(vis[i] || j == f) continue;
			DFS2(j, u);
			sze[u] += sze[j];
			if(max(sze[j], nn - sze[j]) < res) {
				res = max(sze[j], nn - sze[j]), root = i;
			}
		}
	} 
	
	void DFS3(int u, int f, ll dis, ll dep, int type) {
		//更新每个点的边分树信息
		if(u <= n) {
			if(!lst[u]) {
				tr[u] = ++tot;
			}
			else {
				ch[lst[u]][lstp[u]] = ++tot;
			}
			sz[tot][type]++;
			sum[tot][type] = dep + dis;
			lst[u] = tot;
			lstp[u] = type;
		} 
		for(int i = t2.h[u]; ~i; i = t2.ne[i]) {
			int j = t2.e[i];
			if(j == f || vis[i]) continue;
			DFS3(j, u, dis, dep + t2.w[i], type);
		}
	}
	
	void DFS4(int id) {//边分治 
		vis[id] = vis[id ^ 1] = 1;
		int L = t2.e[id], R = t2.e[id ^ 1];
		DFS2(L, R), nn = sze[L], res = 0x3f3f3f3f, root = -1, DFS2(L, R);
		int rootl = root;
		DFS2(R, L), nn = sze[R], res = 0x3f3f3f3f, root = -1, DFS2(R, L);
		int rootr = root;
		DFS3(L, R, 0, 0, 0), DFS3(R, L, t2.w[id], 0, 1);
		if(~rootl) DFS4(rootl);
		if(~rootr) DFS4(rootr); 
	}
}

using namespace conquer;

int merge(int x, int y) {
	if(!x || !y) return x + y;
	int cur = ++tot;
	sum[cur][0] = sum[x][0] + sum[y][0];
	sum[cur][1] = sum[x][1] + sum[y][1];
	sz[cur][0] = sz[x][0] + sz[y][0];
	sz[cur][1] = sz[x][1] + sz[y][1];
	ch[cur][0] = merge(ch[x][0], ch[y][0]);
	ch[cur][1] = merge(ch[x][1], ch[y][1]);
	return cur; 
}

ll query(int x, int y) {
	if(!x || !y) return 0;
	if(sz[y][0]) {
		return sum[x][1] + sz[x][1] * sum[y][0] + query(ch[x][0], ch[y][0]); 
	}
	if(sz[y][1]) {
		return sum[x][0] + sz[x][0] * sum[y][1] + query(ch[x][1], ch[y][1]);
	}
	return 0;
}

int main() {
	read(n, q);
	S = n;
	for(int i = 1; i <= n; i++) {
		read(aa[i]);
	}
	for(int i = 1; i < n; i++) {
		read(u, v, w), t1.add(u, v, w), t1.add(v, u, w);
	}
	DFS1(1, 0), res = 0x3f3f3f3f, conquer::root = -1, DFS2(1, 0);
	if(~conquer::root) {
		DFS4(conquer::root);
	}
	for(int i = 1; i <= n; i++) {
		::root[i] = merge(::root[i - 1], tr[aa[i]]); 
	}
	while(q--) {
		read(t);
		if(t == 1) {
			read(a, b, c);
			a ^= (lstans & ((1 << 30) - 1));
			b ^= (lstans & ((1 << 30) - 1));
			c ^= (lstans & ((1 << 30) - 1));//l, r, v
			printf("%lld\n", lstans = (query(::root[b], tr[c]) - query(::root[a - 1], tr[c]))); 
		}
		else {
			read(a);
			a ^= (lstans & ((1 << 30) - 1));
			::root[a] = merge(::root[a - 1], tr[aa[a + 1]]);
			swap(aa[a], aa[a + 1]);
		}
	}
}

/*
	start coding at:2023/12/8 10:40
	finish debugging at:2023/12/8 11:47
	stubid mistakes:query没有乘以对应系数,S = n写在最后面,没有更新lstans,root[a]应该由root[a - 1]合并而来 
*/

/*
  吾日三省吾身:
  你排序了吗?
  你取模了吗?
  你用%lld输出long long 了吗?
  1LL<<x写对了吗?
  判断=0用了abs()吗?
  算组合数判了a<b吗?
  线段树build()了吗?
  .size()加了(signed)吗?
  树链剖分的DFS序是在第二次更新的吗?
  修改在询问前面吗?
  线段树合并到叶子结点return了吗?
  __builtin_ctz后面需要加ll吗?
*/


posted @   cool_milo  阅读(107)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示