题解: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;
}

浙公网安备 33010602011771号