A
B

HS_fu3的语录 题解

题目:HS_fu3的语录

目录

树剖 + 线段树
LCT

这个题可以用 LCT 在线用更优的时间复杂度 (\(O(n\log{n})\) )通过本题,但是本蒟蒻不会。

所以选用了更加简单的离线做法(也更慢):树剖 + 线段树

请大家阅读。

先放题解代码(码风比较独特,请见谅):点击查看代码
#include<bits/stdc++.h>
#define ent putchar('\n')
#define con putchar(' ')
#define int long long 
#define lid (id << 1) 
#define rid (id << 1 | 1)
#define pushup(id) tr[id].sum = tr[lid].sum + tr[rid].sum
#define add(u,v) to[++ tot] = v,nxt[tot] = h[u],h[u] = tot
#define Blue_Archive return 0
using namespace std;
const int N = 2e5 + 3;
const int M = 4e5 + 3; 

int n;
int m;
int rt;
int tot;
int cnt;
int cntq;
int h[N];
int a[N];
int f[N];
int ff[N];
int fa[N];
int to[M];
int rk[N];
int nxt[M];
int siz[N];
int dep[N];
int dfn[N];
int top[N];
int son[N];

struct miku
{
	int l,r,sum;
}tr[N * 4];

struct mika
{
	int op;
	int x;
	int y;
	int tx;
	int ty;
}q[N];

struct youka
{
	int val;
	int rt;
}ans[N];

inline int read()
{
	int k = 0,f = 1;
	char c = getchar();
	while(c < '0' || c > '9')
	{
		if(c == '-') f = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9') k = (k << 3) + (k << 1) + c - '0',c = getchar();
	return k * f;
}

inline void write(int x)
{
	if(x < 0) putchar('-'),x = -x;
	if(x > 9) write(x / 10);
	putchar(x % 10 + '0');
}

inline int find(int x){return ff[x] == x ? x : ff[x] = find(ff[x]);}

inline void build(int id,int l,int r) 
{
	tr[id].l = l; 
	tr[id].r = r;
	if(l == r) 
	{
		tr[id].sum = a[rk[l]];
		return;
	}
	int mid = (l + r) >> 1;
	build(lid,l,mid);
	build(rid,mid + 1,r);
	pushup(id);
}

inline void dfs1(int u,int f) 
{
	fa[u] = f; 
	dep[u] = dep[f] + 1; 
	siz[u] = 1;
	int maxson = -1;
	for(int i = h[u],v;i;i = nxt[i])
	{
		v = to[i];
		if(v == f) continue;
		dfs1(v,u);
		siz[u] += siz[v];
		if(siz[v] > maxson) maxson = siz[v],son[u] = v;
	}
}

inline void dfs2(int u,int tp) 
{
	dfn[u] = ++ cnt; 
	rk[cnt] = u; 
	top[u] = tp;
	if(!son[u]) return;
	dfs2(son[u],tp);
	for(int i = h[u],v;i;i = nxt[i])
	{
		v = to[i];
		if(v == fa[u] || v == son[u]) continue;
		dfs2(v,v);
	}
}

inline void update(int id,int pos,int val)
{
	if(tr[id].l == tr[id].r)
	{
		tr[id].sum = val;
		return;
	}
	int mid = (tr[id].l + tr[id].r) >> 1;
	if(mid >= pos) update(lid, pos, val);
	else update(rid, pos, val);
	pushup(id);
}

inline int query(int id,int l,int r) 
{
	if(tr[id].l > r || tr[id].r < l) return 0;
	if(l <= tr[id].l && tr[id].r <= r) return tr[id].sum;
	return query(lid,l,r) + query(rid,l,r);
}

inline int query_path(int x,int y) 
{
	int res = 0;
	while(top[x] != top[y]) 
	{
		if(dep[top[x]] < dep[top[y]]) swap(x,y);
		res += query(1,dfn[top[x]],dfn[x]);
		x = fa[top[x]];
	}
	if(dep[x] > dep[y]) swap(x,y);
	res += query(1,dfn[x],dfn[y]);
	return res;
}

signed main()
{
	// freopen("data.in","r",stdin);
	// freopen("data.out","w",stdout);
	n = read();
	m = read();
	for(int i = 1;i <= n;i ++)
	{
		a[i] = read();
		f[i] = i;
		ff[i] = i;
	}
	char op;
	for(int i = 1;i <= m;i ++)
	{
		cin >> op;
		if(op == 'm')
		{
			q[i].op = 1;
			q[i].x = read();
			q[i].y = read();
			q[i].tx = find(q[i].x);
			q[i].ty = find(q[i].y);
			if(q[i].tx == q[i].ty) continue;
			f[q[i].ty] = q[i].tx;
			ff[q[i].ty] = q[i].tx;
		}
		if(op == 'f')
		{
			q[i].op = 2;
			q[i].x = read();
			q[i].tx = find(q[i].x);
		}
		if(op == 'c')
		{
			q[i].op = 3;
			q[i].x = read();
			q[i].y = read();
		}
	}
	rt = n + 1;
	for(int i = 1;i <= n;i ++)
	{
		if(i == f[i])
		{
			add(i,rt);
			add(rt,i);
		}
		else 
		{
			add(i,f[i]);
			add(f[i],i);
		}
	}
	dfs1(rt,0);
	dfs2(rt,rt);
	build(1,1,n + 1);
	for(int i = 1;i <= m;i ++)
	{
		if(q[i].op == 1)//meg
		{
			ans[++ cntq].val = query_path(q[i].x,q[i].tx);
			ans[cntq].val += query_path(q[i].y,q[i].ty);
		}
		if(q[i].op == 2)//query
		{
			ans[++ cntq].val = query_path(q[i].x,q[i].tx);
			ans[cntq].rt = q[i].tx;
		}
		if(q[i].op == 3)//change
		{
			update(1,dfn[q[i].x],q[i].y);
		}
	}
	for(int i = 1;i <= cntq;i ++)
	{
		if(ans[i].rt) 
		{
			write(ans[i].rt);con;write(ans[i].val);ent;
		}
		else 
		{
			write(ans[i].val);ent;
		}
	}
	Blue_Archive; 
}

正文开始

读题发现,这个题就是求该节点该节点所在连通块的根节点的路径点权和

然后动态维护这个森林。进行并查集操作。

善良的出题人(在下)给了注意:数据不保证最终一定是一棵树。(赛时因为没有想到狂调 1 hour)

所以我们可以从这些点的最终形态入手。

首先我们要查询最短路径长,所以考虑树剖。

那么既然要树剖,总得有棵树啊。

然后就可以自然而然地想到最终形态。

既然最后是森林(显然可得)。

如果对每棵树进行剖分的话,时间复杂度直接爆炸。

于是我们充分发挥人类智慧,将每棵树的根连一个超级根

把森林变成树。

然后我们就可以愉快地树剖了。

那么观察操作 \(merge\),发现这棵树与另一棵树合并后,这两棵树的形态均未改变,在这棵树上的 \(find\) 值,与在合并之后的树上的 \(find\) 值是一样的。

于是我们就可以记录此时该节点所处树的根

然后再把最后的树建出来。

在最后的树上跑最短路径。

对于点权:通过树剖我们已经把整个序列的DFS序搞出来了。

之后直接用一棵线段树维护区间和就可以了。

时间复杂度分析:

树剖:预处理:\(O(n)\),每次查询: \(O(\log{n})\)

线段树:查询:\(O(\log{n})\),修改:\(O(\log{n})\)

总时间复杂度:\(O(n\log^2{n})\)

可以通过此题,跑的飞快!

好了,感谢观看本文,感谢看或做了本题,谢谢大家!

posted @ 2025-08-17 18:47  MyShiroko  阅读(26)  评论(2)    收藏  举报