题解:P3637 方程组

LCT

这里我们使用 LCT 解决本题。

如果你没有学习过这种数据结构,建议完成P3690 【模板】动态树(LCT)进行学习。

我们应该维护什么

本题的思路可以说是 LCT 维护边权的模板题,添加一个方程 \(x_a-x_b\equiv c\pmod K\) 相当于添加一条 \(b\rightarrow a\) 的有向边,边权为 \(c\),然后再添加一条 \(a\rightarrow b\) 的有向边,边权为 \(-c\),删除同理。查询一个 \(x_b \bmod K\) 就相当于查询 \(a\rightarrow b\) 的有向路径上的边权和加上 \(x_a\) 的值。

我们应该怎么维护

你说得对,但是 LCT 这一数据结构是用于维护点权的,怎么将其转化为边权呢?

我们采用的方法是化边为点,即对于每个边转化为一个向它的两个端点连边的点,并把边权放在这个点上。

这就基本完成了模型的构造,剩下就是一些细节了。

需要注意的是:

  • 由于模型的特殊性,reverse 操作时需要将节点的 sum 和 val 都变成原来的相反数。
  • 注意一些操作的特判。如在操作 2 是否能删除的地方需要细致分类讨论,分讨过程附上了详细的注释。

Code

奉上数组版 LCT。

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,m,k,q;
struct Node{int ch[2],fa,sum,val,tg;}t[N];
#define lc t[x].ch[0]
#define rc t[x].ch[1]
bool isRoot(int x){
	int g=t[x].fa;
	return t[g].ch[0]!=x&&t[g].ch[1]!=x;
}
void pushup(int x){
	t[x].sum=t[x].val+t[lc].sum+t[rc].sum;
}
void reverse(int x){
	if(!x)return ;
	swap(lc,rc);
	t[x].val=-t[x].val;
	t[x].sum=-t[x].sum;
	t[x].tg^=1;
}
void pushdown(int x){
	if(t[x].tg){
		reverse(lc);
		reverse(rc);
		t[x].tg=0;
	}
}
void push(int x){
	if(!isRoot(x))push(t[x].fa);
	pushdown(x);
}
void rotate(int x){
	int y=t[x].fa;
	int z=t[y].fa;
	bool k=(t[y].ch[1]==x);
	if(!isRoot(y))t[z].ch[t[z].ch[1]==y]=x;
	t[x].fa=z;
	t[y].ch[k]=t[x].ch[k^1];
	if(t[x].ch[k^1])t[t[x].ch[k^1]].fa=y;
	t[y].fa=x;
	t[x].ch[k^1]=y;
	pushup(y);
}
void splay(int x){
	int y,z;
	push(x);
	while(!isRoot(x)){
		y=t[x].fa,z=t[y].fa;
		if(!isRoot(y))
			(t[z].ch[0]==y)^(t[y].ch[0]==x)?rotate(x):rotate(y);
		rotate(x);
	}
	pushup(x);
}
void access(int x){
	for(int child=0;x;child=x,x=t[x].fa){
		splay(x);
		rc=child;
		pushup(x);
	}
}
void makeroot(int x){
	access(x);
	splay(x);
	reverse(x);
}
void split(int x,int y){
	makeroot(x);
	access(y);
	splay(y);
}
void link(int x,int y){
	makeroot(x);
	t[x].fa=y;
}
int findroot(int x){
	access(x);splay(x);
	while(lc)pushdown(x),x=lc;
	return x;
}
#undef lc
#undef rc
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m>>k>>q;
	for(int i=1;i<=m;i++){
		int a,b,c;
		cin>>a>>b>>c;
		int mid=n+i;
		t[mid].val=t[mid].sum=c;
		link(mid,a);
		link(b,mid);
	}
	for(int i=1;i<=q;i++){
		int opt,a,b,c;
		cin>>opt>>a>>b;
		if(opt==1){
			cin>>c;
			int mid=n+m+i;
			t[mid].val=t[mid].sum=c;
			link(mid,a);
			link(b,mid);
		}
		else if(opt==2){
			makeroot(a);
			access(b);
			splay(b);
			if(t[b].ch[0]==a){
				if(t[a].ch[0])continue;
				if(!t[a].ch[1])continue;
				if(t[t[a].ch[1]].ch[0])continue;
				if(t[t[a].ch[1]].ch[1])continue;
				t[a].fa=0;
				t[b].ch[0]=0;
				t[t[a].ch[1]].val=t[t[a].ch[1]].sum=0;
				t[a].ch[1]=0;
				pushup(b);pushup(a);
				/*
				这里 makeroot(a), access(b), splay(b) 以后
				如果 a,b 通过一个中间节点 c 连接,那么其
				形态必定是 b 有且仅有一个左儿子 a
				a 有且仅有一个右儿子 且该右儿子没有子孙
				这里的特判就是判断了这些东西 
				如果与该形态不同,要么就是两点不直接相连
				要么就是不联通 
				*/
			}
		}
		else {
			cin>>c;
			if(findroot(a)!=findroot(b)){
				cout<<'-'<<'1'<<'\n';
				continue;
			}
			split(a,b);
			cout<<((t[b].sum+c)%k+k)%k<<'\n';
		}
	}
	return 0;
}
posted @ 2025-08-11 10:50  TBSF_0207  阅读(18)  评论(0)    收藏  举报