LCT(link-cut tree)
LCT(link-cut tree)
时隔多年,我终于学会了\(LCT\),真的高兴坏了
详细解释都在代码注释中,再不行就看大佬的博客
同样是为了方便自己看,因为自己的板子终究是和别人的不一样
code(注释)
//LCT中的splay是以深度为大小,满足平衡树性质的,也就是说左边的深度小于右边
//分为实边和虚边,所有在splay中的边都是实边
//虚边是通过splay树根的父亲找到的,虚边只有儿子认爹,爹的儿子中没有虚边对应的儿子
//不要在意splay中的父子关系混乱,因为按照深度排序,操作得当,总能还原原树的边
//每一颗splay连向其他splay的虚边,在原树上都是由当前splay中深度最小的点,指向父亲的
//对于splay的任意子树也满足上一行所述的性质,因为深度满足平衡树性质
#include<bits/stdc++.h>
using namespace std;
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
int s=0,t=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*t;
}
const int N=1e5+5;
int n,q;
struct LCT{
struct POT{
int sum,val,son[2],fa;
bool rev;//是否翻转
}tr[N];
int pth[N],pt;//到根的路径上需要全部pushdown,用这个东西模拟栈,防止炸掉
void pushup(int x){
tr[x].sum=tr[tr[x].son[0]].sum^tr[tr[x].son[1]].sum^tr[x].val;
return ;
}
void pushr(int x){
//对单独节点翻转儿子,并打上翻转标记,翻转标记只在需要换根的时候需要
swap(tr[x].son[0],tr[x].son[1]);
tr[x].rev^=1;
}
void pushdown(int x){
if(!tr[x].rev)return ;
if(tr[x].son[0])pushr(tr[x].son[0]);
if(tr[x].son[1])pushr(tr[x].son[1]);
tr[x].rev=0;return ;
}
bool nroot(int x){return tr[tr[x].fa].son[0]==x||tr[tr[x].fa].son[1]==x;}
//判断是否是当前splay的跟,是的话返回false,因为认爹不认儿子,看看爹有没有自己这个儿子就行了
int get(int x){return x==tr[tr[x].fa].son[1];}
void rotate(int x){
int y=tr[x].fa,z=tr[y].fa;
int xpos=get(x),ypos=get(y);
if(nroot(y))tr[z].son[ypos]=x;//不能更改0节点的信息,必须要判断
tr[x].fa=z;tr[y].fa=x;
tr[y].son[xpos]=tr[x].son[xpos^1];
tr[tr[x].son[xpos^1]].fa=y;
tr[x].son[xpos^1]=y;
pushup(y);return ;
//此处不需要pushup(x),因为x会一直跳上去,所以可以在splay()中最后pushup一下就好了
}
void splay(int x){
pt=0;int now=x;pth[++pt]=now;
while(nroot(now))pth[++pt]=now=tr[now].fa;
while(pt)pushdown(pth[pt--]);
//从跟开始一直pushdown下来,手工栈
while(nroot(x)){
int y=tr[x].fa,z=tr[y].fa;
int xpos=get(x),ypos=get(y);
if(nroot(y)){//判断有没有爹
if(xpos==ypos)rotate(y);
else rotate(x);
}
rotate(x);
}
pushup(x);
}
void access(int x){
//打通一条从原树的跟到x节点的路径,端点是跟和x节点
//此时当前splay中只有跟到x节点路径上的点
for(int y=0;x;y=x,x=tr[x].fa)
splay(x),tr[x].son[1]=y,pushup(x);
}
void makeroot(int x){//让x成为原树的树根
access(x);splay(x);pushr(x);
//此时需要翻转区间,深度翻转,因为本来x是最低的,现在是最高的,直接整体翻转就行了
//并且此时x一定没有右儿子,因为在这一颗splay中它是深度最低的点
}
int findroot(int x){
//找到原树的根,先把x到跟的路径打通,然后把x转上来,接着就直接往左找
access(x);splay(x);
while(tr[x].son[0])pushdown(x),x=tr[x].son[0];
splay(x);return x;
}
void split(int x,int y){
//提取x到y的路径,并且使路径所在splay根为y
makeroot(x);access(y);splay(y);
}
bool link(int x,int y){
//连边,返回值为是否链接成功,成功返回true
makeroot(x);
if(findroot(y)==x)return false;//判断是不是在一棵树内,在的话不能继续连边
tr[x].fa=y;return true;
//必须是x向y连边,因为此时x是跟,但是y不一定是
//此时连的是虚边,不需要pushup
}
bool cut(int x,int y){
//断边,返回值为是否断开成功,成功返回true
makeroot(x);//这里断开了与y链接的实边,保证要被cut掉的一定是虚边,为下面判断做准备
if(findroot(y)!=x||tr[y].fa!=x||tr[y].son[0])return false;
//上面三个条件缺一不可,1、在一棵树内
//2、y的爹是x。因为此时findroot了,y是所在splay的跟,父亲一定是x
//3、y的深度减一一定是x,但是现在x是跟,所以y的左儿子不能有东西
tr[y].fa=tr[x].son[1]=0;pushup(x);
//有可能少了一个点,必须要pushup,不知道此时的连边是实边还是虚边,所以必须pushup
return true;
}
}lct;
signed main(){
n=read();q=read();
fo(i,1,n)lct.tr[i].val=read();
while(q--){
int tp=read(),x=read(),y=read();
if(tp==0)lct.split(x,y),printf("%d\n",lct.tr[y].sum);
if(tp==1)lct.link(x,y);
if(tp==2)lct.cut(x,y);
if(tp==3)lct.splay(x),lct.tr[x].val=y,lct.pushup(x);
}
}
code
#include<bits/stdc++.h>
using namespace std;
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
int s=0,t=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*t;
}
const int N=1e5+5;
int n,q;
struct LCT{
struct POT{
int sum,val,son[2],fa;
bool rev;
}tr[N];
int pth[N],pt;
void pushup(int x){
tr[x].sum=tr[tr[x].son[0]].sum^tr[tr[x].son[1]].sum^tr[x].val;
return ;
}
void pushr(int x){
swap(tr[x].son[0],tr[x].son[1]);
tr[x].rev^=1;
}
void pushdown(int x){
if(!tr[x].rev)return ;
if(tr[x].son[0])pushr(tr[x].son[0]);
if(tr[x].son[1])pushr(tr[x].son[1]);
tr[x].rev=0;return ;
}
bool nroot(int x){return tr[tr[x].fa].son[0]==x||tr[tr[x].fa].son[1]==x;}
int get(int x){return x==tr[tr[x].fa].son[1];}
void rotate(int x){
int y=tr[x].fa,z=tr[y].fa;
int xpos=get(x),ypos=get(y);
if(nroot(y))tr[z].son[ypos]=x;
tr[x].fa=z;tr[y].fa=x;
tr[y].son[xpos]=tr[x].son[xpos^1];
tr[tr[x].son[xpos^1]].fa=y;
tr[x].son[xpos^1]=y;
pushup(y);return ;
}
void splay(int x){
pt=0;int now=x;pth[++pt]=now;
while(nroot(now))pth[++pt]=now=tr[now].fa;
while(pt)pushdown(pth[pt--]);
while(nroot(x)){
int y=tr[x].fa,z=tr[y].fa;
int xpos=get(x),ypos=get(y);
if(nroot(y)){
if(xpos==ypos)rotate(y);
else rotate(x);
}
rotate(x);
}
pushup(x);
}
void access(int x){
for(int y=0;x;y=x,x=tr[x].fa)
splay(x),tr[x].son[1]=y,pushup(x);
}
void makeroot(int x){
access(x);splay(x);pushr(x);
}
int findroot(int x){
access(x);splay(x);
while(tr[x].son[0])pushdown(x),x=tr[x].son[0];
splay(x);return x;
}
void split(int x,int y){
makeroot(x);access(y);splay(y);
}
bool link(int x,int y){
makeroot(x);
if(findroot(y)==x)return false;
tr[x].fa=y;return true;
}
bool cut(int x,int y){
makeroot(x);
if(findroot(y)!=x||tr[y].fa!=x||tr[y].son[0])return false;
tr[y].fa=tr[x].son[1]=0;pushup(x);
return true;
}
}lct;
signed main(){
n=read();q=read();
fo(i,1,n)lct.tr[i].val=read();
while(q--){
int tp=read(),x=read(),y=read();
if(tp==0)lct.split(x,y),printf("%d\n",lct.tr[y].sum);
if(tp==1)lct.link(x,y);
if(tp==2)lct.cut(x,y);
if(tp==3)lct.splay(x),lct.tr[x].val=y,lct.pushup(x);
}
}
关于LCT的活用
一般用\(LCT\)维护链的信息
然而这里面所有操作的基础就是\(access\)
所以当我们无法维护信息的时候,可以试着更改\(access\)操作,有意想不到的效果
例题
这个就是典型的维护边权的题
首先维护边权的话,我们只需要把边也当成点
连边的话,就从这个点向连接的两个点都连一个边,断边同理
用\(map\)访问原树两个点之间对应哪个边
这样空间要开两倍,然后注意维护权值的时候判断一下是否是边所对应的点
这个题就是动态维护最小生成树
我们把询问反过来,看做是一条一条的加边
这样如果两个点之间已经有连边了,那就询问权值最大的边
比较当前边和询问的最大值,如果当前边权值更小的话,删掉询问出来的边,加入当前边
code
#include<bits/stdc++.h>
using namespace std;
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
int s=0,t=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*t;
}
const int inf=0x3f3f3f3f;
const int N=1e6+1e5+5;
int n,m,q;
struct LCT{
struct POT{
int mx,id,val,son[2],fa;
bool rev;
POT(){val=-inf;}
}tr[N];
int pth[N],pt;
void pushup(int x){
tr[x].mx=tr[x].val;tr[x].id=x;
if(tr[tr[x].son[0]].mx>tr[x].mx){
tr[x].mx=tr[tr[x].son[0]].mx;
tr[x].id=tr[tr[x].son[0]].id;
}
if(tr[tr[x].son[1]].mx>tr[x].mx){
tr[x].mx=tr[tr[x].son[1]].mx;
tr[x].id=tr[tr[x].son[1]].id;
}
return ;
}
void pushr(int x){
swap(tr[x].son[0],tr[x].son[1]);
tr[x].rev^=1;
}
void pushdown(int x){
if(tr[x].rev){
if(tr[x].son[0])pushr(tr[x].son[0]);
if(tr[x].son[1])pushr(tr[x].son[1]);
tr[x].rev=0;
}
return ;
}
bool nroot(int x){return tr[tr[x].fa].son[0]==x||tr[tr[x].fa].son[1]==x;}
int get(int x){return tr[tr[x].fa].son[1]==x;}
void rotate(int x){
int y=tr[x].fa,z=tr[y].fa;
int xpos=get(x),ypos=get(y);
if(nroot(y))tr[z].son[ypos]=x;
tr[x].fa=z;tr[y].fa=x;
tr[y].son[xpos]=tr[x].son[xpos^1];
tr[tr[x].son[xpos^1]].fa=y;
tr[x].son[xpos^1]=y;
pushup(y);return ;
}
void splay(int x){
int now=x;pth[++pt]=now;
while(nroot(now))pth[++pt]=now=tr[now].fa;
while(pt)pushdown(pth[pt--]);
while(nroot(x)){
int y=tr[x].fa,z=tr[y].fa;
int xpos=get(x),ypos=get(y);
if(nroot(y)){
if(xpos==ypos)rotate(y);
else rotate(x);
}
rotate(x);
}
pushup(x);
}
void access(int x){
for(int y=0;x;y=x,x=tr[x].fa)
splay(x),tr[x].son[1]=y,pushup(x);
}
void makeroot(int x){
access(x);splay(x);pushr(x);
}
int findroot(int x){
access(x);splay(x);
while(tr[x].son[0])pushdown(x),x=tr[x].son[0];
splay(x);return x;
}
void split(int x,int y){
makeroot(x);access(y);splay(y);
}
bool link(int x,int y){
makeroot(x);
if(findroot(y)==x)return false;
tr[x].fa=y;return true;
}
bool cut(int x,int y){
makeroot(x);
if(findroot(y)!=x||tr[y].fa!=x||tr[y].son[0])return false;
tr[y].fa=tr[x].son[1]=0;pushup(x);return true;
}
bool lt(int x,int y){
makeroot(x);
if(findroot(y)==x)return true;
else return false;
}
}lct;
pair<int,int> e[N];
map<pair<int,int>,int> mp;
int val[N];
bool vis[N];
struct QUS{
int tp,x,y;
}qus[N];
int ans[N],an;
signed main(){
n=read();m=read();q=read();
fo(i,1,m){
int x=read(),y=read();
e[i]=make_pair(x,y);
val[i]=read();
lct.tr[i+n].val=val[i];
lct.tr[i+n].id=i+n;
lct.pushup(i+n);
mp[e[i]]=i;
swap(e[i].first,e[i].second);
mp[e[i]]=i;
}
fo(i,1,q){
int tp=read(),x=read(),y=read();
qus[i]=QUS{tp,x,y};
if(qus[i].tp==2)vis[mp[make_pair(qus[i].x,qus[i].y)]]=true;
}
fo(i,1,m){
if(vis[i])continue;
int x=e[i].first,y=e[i].second;
if(lct.lt(x,y)){
lct.split(x,y);
if(lct.tr[y].mx<=val[i])continue;
else {
int id=lct.tr[y].id-n;
lct.cut(e[id].first,id+n);
lct.cut(e[id].second,id+n);
lct.link(x,i+n);
lct.link(y,i+n);
}
}
else {
lct.link(x,i+n);
lct.link(y,i+n);
}
}
while(q){
int x=qus[q].x,y=qus[q].y;
if(qus[q].tp==1){
lct.split(x,y);
ans[++an]=lct.tr[y].mx;
}
else {
int id=mp[make_pair(x,y)];
lct.split(x,y);
if(val[id]>=lct.tr[y].mx);
else {
int mid=lct.tr[y].id-n;
lct.cut(e[mid].first,mid+n);
lct.cut(e[mid].second,mid+n);
lct.link(x,id+n);
lct.link(y,id+n);
}
}
q--;
}
fu(i,an,1)printf("%d\n",ans[i]);
}
两颗\(LCT\)对着跑的感觉真不赖
翻转一条边上的权值,但是不翻转位置
看到第一句话的时候,我们可以立刻马上想到是\(LCT\)
但是看到第二句话的时候,发现好像维护不了啊
确实,但是两颗就行了......
没有什么是两颗解决不了的,两颗线段树,两个堆,两颗平衡树......
我们用一颗维护树的形态,也就是我们平时写的\(LCT\)
另一颗......其实也不能叫他\(LCT\)因为两颗\(splay\)之间是没有联系的,也就是说没有虚边
就叫他小树林吧......
小树林里\(splay\)的联通状态是一样的,也就说中序遍历之后和形态树是一一对应的
我们也就是这样维护每一个点的权值的
于是我们有一些地方写的时候有一点小差距
我们在形态树中定义\(rt\)为当前点对应的小树林中的树的树根是哪个
于是我们在\(splay\)后要把根也变一下,注意这个根要实时变化,因为我们的操作太多了
不断地\(findrt\),这个是找每一颗\(splay\)的根
\(access\)的时候,注意带着小树林里的树连通性一起变,很麻烦......
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
int s=0,t=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*t;
}
const int inf=1e18;
const int N=5e4+5;
struct V{
struct POT{
int sum,mx,mn,val,siz,tag;
int son[2],fa;
bool rev;
POT(){mx=-inf;mn=100000000;}
}tr[N];
int pth[N],pt;
void init(int x){tr[x].val=tr[x].sum=tr[x].mn=tr[x].mx=0;tr[x].siz=1;}
void pushup(int x){
// if(x==0)return ;
tr[x].siz=tr[tr[x].son[0]].siz+tr[tr[x].son[1]].siz+1;
tr[x].sum=tr[tr[x].son[0]].sum+tr[tr[x].son[1]].sum+tr[x].val;
tr[x].mx=max(tr[tr[x].son[0]].mx,max(tr[tr[x].son[1]].mx,tr[x].val));
tr[x].mn=min(tr[tr[x].son[0]].mn,min(tr[tr[x].son[1]].mn,tr[x].val));
return ;
}
void pushr(int x){
swap(tr[x].son[0],tr[x].son[1]);
tr[x].rev^=1;
}
void pusht(int x,int c){
tr[x].sum+=tr[x].siz*c;
tr[x].val+=c;tr[x].tag+=c;
tr[x].mn+=c;tr[x].mx+=c;
return ;
}
void pushdown(int x){
if(tr[x].rev){
if(tr[x].son[0])pushr(tr[x].son[0]);
if(tr[x].son[1])pushr(tr[x].son[1]);
tr[x].rev=0;
}
if(tr[x].tag){
if(tr[x].son[0])pusht(tr[x].son[0],tr[x].tag);
if(tr[x].son[1])pusht(tr[x].son[1],tr[x].tag);
tr[x].tag=0;
}
return ;
}
bool nroot(int x){return tr[tr[x].fa].son[0]==x||tr[tr[x].fa].son[1]==x;}
int get(int x){return tr[tr[x].fa].son[1]==x;}
void rotate(int x){
int y=tr[x].fa,z=tr[y].fa;
int xpos=get(x),ypos=get(y);
if(nroot(y))tr[z].son[ypos]=x;
tr[x].fa=z;tr[y].fa=x;
tr[y].son[xpos]=tr[x].son[xpos^1];
tr[tr[x].son[xpos^1]].fa=y;
tr[x].son[xpos^1]=y;
pushup(y);pushup(x);return ;
}
void splay(int x){
int now=x;pth[++pt]=now;
while(nroot(now))pth[++pt]=now=tr[now].fa;//cout<<now<<" "<<tr[now].fa<<endl;
// cout<<x<<endl;
// cout<<"X"<<" "<<x<<endl;
while(pt)pushdown(pth[pt--]);
while(nroot(x)){
// cout<<"ZZ"<<" "<<tr[x].fa<<" "<<x<<" "<<endl;
int y=tr[x].fa,z=tr[y].fa;
int xpos=get(x),ypos=get(y);
if(nroot(y)){
if(xpos==ypos)rotate(y);
else rotate(x);
}
rotate(x);
}
pushup(x);
}
int findrt(int &x){
while(tr[x].fa)x=tr[x].fa;
return x;
}
int findrk(int &x,int k){
while(true){
pushdown(x);
if(tr[tr[x].son[0]].siz>=k)x=tr[x].son[0];
else if(tr[tr[x].son[0]].siz+1==k)break;
else k-=(tr[tr[x].son[0]].siz+1),x=tr[x].son[1];
}
return x;
}
void add(int x,int c){pusht(x,c);}
}v;
struct A{
struct POT{
int siz,rt;
int son[2],fa;
bool rev;
POT(){}
}tr[N];
int pth[N],pt;
void init(int x){tr[x].siz=1;tr[x].rt=x;}
void pushup(int x){
tr[x].siz=tr[tr[x].son[0]].siz+tr[tr[x].son[1]].siz+1;
return ;
}
void pushr(int x){
swap(tr[x].son[0],tr[x].son[1]);
tr[x].rev^=1;
}
void pushdown(int x){
if(tr[x].rev){
if(tr[x].son[0])pushr(tr[x].son[0]);
if(tr[x].son[1])pushr(tr[x].son[1]);
tr[x].rev=0;
}
return ;
}
bool nroot(int x){return tr[tr[x].fa].son[0]==x||tr[tr[x].fa].son[1]==x;}
int get(int x){return tr[tr[x].fa].son[1]==x;}
void rotate(int x){
int y=tr[x].fa,z=tr[y].fa;
int xpos=get(x),ypos=get(y);
if(nroot(y))tr[z].son[ypos]=x;
tr[x].fa=z;tr[y].fa=x;
tr[y].son[xpos]=tr[x].son[xpos^1];
tr[tr[x].son[xpos^1]].fa=y;
tr[x].son[xpos^1]=y;
pushup(y);pushup(x);return ;
}
void splay(int x){
int now=x;pth[++pt]=now;
while(nroot(now))pth[++pt]=now=tr[now].fa;
while(pt)pushdown(pth[pt--]);
tr[x].rt=tr[now].rt;
while(nroot(x)){
int y=tr[x].fa,z=tr[y].fa;
int xpos=get(x),ypos=get(y);
if(nroot(y)){
if(xpos==ypos)rotate(y);
else rotate(x);
}
rotate(x);
}
pushup(x);
}
void access(int x){
for(int y=0;x;y=x,x=tr[x].fa){
splay(x);//cout<<"X"<<" "<<x<<" "<<tr[x].rt<<endl;
int xx=v.findrt(tr[x].rt),yy=v.findrt(tr[y].rt);
// if(tr[x].siz!=v.tr[xx].siz)cout<<"SB"<<endl;
// cout<<"XX"<<" "<<xx<<endl;
// cout<<"p"<<endl;
if(!y)yy=0;
// if(v.tr[xx].siz<tr[tr[x].son[0]].siz+1)cout<<"siz"<<" "<<v.tr[xx].siz<<" "<<tr[tr[x].son[0]].siz+1<<endl;
xx=v.findrk(xx,tr[tr[x].son[0]].siz+1);
// cout<<"P"<<endl;
v.splay(xx);tr[x].rt=xx;
// cout<<"K"<<endl;
tr[tr[x].son[1]].rt=v.tr[xx].son[1];
v.tr[v.tr[xx].son[1]].fa=0;
v.tr[yy].fa=xx;
// if(yy==xx)cout<<"you are shit"<<" "<<x<<" "<<tr[y].fa<<" "<<tr[x].son[0]<<" "<<tr[x].son[1]<<" "<<y<<" "<<xx<<" "<<yy<<endl;
v.tr[xx].son[1]=yy;v.pushup(xx);
tr[x].son[1]=y;pushup(x);
}//v.tr[tr[x].rt].fa=0;
}
void makeroot(int x){
access(x);splay(x);
v.splay(tr[x].rt);
// cout<<"siz"<<" "<<tr[x].siz<<" "<<v.tr[tr[x].rt].siz<<endl;
pushr(x);v.pushr(v.findrt(tr[x].rt));
}
void split(int x,int y){
makeroot(x);
access(y);
// cout<<"f"<<" "<<y<<" "<<tr[y].rt<<endl;
splay(y);
// cout<<"a"<<endl;
v.splay(v.findrt(tr[y].rt));
}
void link(int x,int y){
makeroot(x);
tr[x].fa=y;
}
}a;
int n,q;
int to[N*2],nxt[N*2],head[N],rp;
void add_edg(int x,int y){
to[++rp]=y;
nxt[rp]=head[x];
head[x]=rp;
}
void dfs(int x,int f){
a.tr[x].fa=f;
for(int i=head[x];i;i=nxt[i]){
if(to[i]==f)continue;
dfs(to[i],x);
}
}
signed main(){
n=read();q=read();int sb=read();
fo(i,1,n){
a.init(i);v.init(i);
}
fo(i,1,n-1)a.link(read(),read());
// fo(i,1,n-1){
// int x=read(),y=read();
// add_edg(x,y);add_edg(y,x);
// }
// dfs(sb,0);
// cout<<"ZZ"<<endl;
while(q--){
char tp[10];scanf("%s",tp+1);
int x=read(),y=read(),c;
// cout<<q<<endl;
a.split(x,y);
// cout<<"get"<<endl;
a.tr[y].rt=v.findrt(a.tr[y].rt);
// cout<<"SB"<<endl;
//a.tr[y].rt=v.findrt(a.tr[y].rt);
if(tp[1]=='I'&&tp[3]=='c')c=read(),v.add(a.tr[y].rt,c);
if(tp[1]=='S')printf("%lld\n",v.tr[a.tr[y].rt].sum);
if(tp[1]=='M'&&tp[2]=='a')printf("%lld\n",v.tr[a.tr[y].rt].mx);
if(tp[1]=='M'&&tp[2]=='i')printf("%lld\n",v.tr[a.tr[y].rt].mn);
if(tp[1]=='I'&&tp[3]=='v')v.pushr(a.tr[y].rt);
// a.pushup(y);
// v.pushup(a.tr[y].rt);
}
}
两个权值,那就先给其中一个排序
然后维护另一个,用啥,当然是\(LCT\)
于是我们一边插边一边统计答案,看到\(b\)小的就插进去,对了先给\(a\)排序
于是就完了,这个题
code
#include<bits/stdc++.h>
using namespace std;
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
int read(){
int s=0,t=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*t;
}
const int N=1e5+5e4+5;
struct LCT{
struct POT{
int mx,id,val,son[2],fa;
bool rev;
}tr[N];
int pth[N],pt;
void pushup(int x){
tr[x].mx=tr[x].val;tr[x].id=x;
if(tr[tr[x].son[0]].mx>tr[x].mx){
tr[x].mx=tr[tr[x].son[0]].mx;
tr[x].id=tr[tr[x].son[0]].id;
}
if(tr[tr[x].son[1]].mx>tr[x].mx){
tr[x].mx=tr[tr[x].son[1]].mx;
tr[x].id=tr[tr[x].son[1]].id;
}
return ;
}
void pushr(int x){
swap(tr[x].son[0],tr[x].son[1]);
tr[x].rev^=1;
return ;
}
void pushdown(int x){
if(tr[x].rev){
if(tr[x].son[0])pushr(tr[x].son[0]);
if(tr[x].son[1])pushr(tr[x].son[1]);
tr[x].rev=0;
}
return ;
}
bool nroot(int x){return tr[tr[x].fa].son[0]==x||tr[tr[x].fa].son[1]==x;}
int get(int x){return tr[tr[x].fa].son[1]==x;}
void rotate(int x){
int y=tr[x].fa,z=tr[y].fa;
int xpos=get(x),ypos=get(y);
if(nroot(y))tr[z].son[ypos]=x;
tr[x].fa=z;tr[y].fa=x;
tr[y].son[xpos]=tr[x].son[xpos^1];
tr[tr[x].son[xpos^1]].fa=y;
tr[x].son[xpos^1]=y;
pushup(y);return ;
}
void splay(int x){
int now=x;pth[++pt]=now;
while(nroot(now))pth[++pt]=now=tr[now].fa;
while(pt)pushdown(pth[pt--]);
while(nroot(x)){
int y=tr[x].fa,z=tr[y].fa;
int xpos=get(x),ypos=get(y);
if(nroot(y)){
if(xpos==ypos)rotate(y);
else rotate(x);
}
rotate(x);
}
pushup(x);return ;
}
void access(int x){
for(int y=0;x;y=x,x=tr[x].fa)
splay(x),tr[x].son[1]=y,pushup(x);
}
void makeroot(int x){
access(x);splay(x);pushr(x);
}
int findroot(int x){
access(x);splay(x);
while(tr[x].son[0])pushdown(x),x=tr[x].son[0];
splay(x);return x;
}
void split(int x,int y){
makeroot(x);access(y);splay(y);
}
bool link(int x,int y){
makeroot(x);
if(findroot(y)==x)return false;
tr[x].fa=y;return true;
}
bool cut(int x,int y){
makeroot(x);
if(findroot(y)!=x||tr[y].fa!=x||tr[y].son[0])return false;
tr[y].fa=tr[x].son[1]=0;pushup(x);return true;
}
bool lt(int x,int y){
makeroot(x);
if(findroot(y)==x)return true;
else return false;
}
}lct;
int n,m,ans=0x3f3f3f3f;
struct E{int x,y,a,b;}e[N];
bool com(E a,E b){return a.a<b.a;}
signed main(){
n=read();m=read();
fo(i,1,m)e[i].x=read(),e[i].y=read(),e[i].a=read(),e[i].b=read();
sort(e+1,e+m+1,com);
fo(i,1,m){
// cout<<e[i].a<<" "<<e[i].b<<endl;
if(lct.lt(e[i].x,e[i].y)){
lct.split(e[i].x,e[i].y);
if(lct.tr[e[i].y].mx<=e[i].b)continue;
lct.tr[i+n].val=e[i].b;lct.pushup(i+n);
int id=lct.tr[e[i].y].id-n;
lct.cut(e[id].x,id+n);
lct.cut(e[id].y,id+n);
lct.link(e[i].x,i+n);
lct.link(e[i].y,i+n);
}
else {
lct.tr[i+n].val=e[i].b;lct.pushup(i+n);
lct.link(e[i].x,i+n);
lct.link(e[i].y,i+n);
}
if(lct.lt(1,n))lct.split(1,n),ans=min(ans,lct.tr[n].mx+e[i].a);
}
printf("%d",ans==0x3f3f3f3f?-1:ans);
}
庆祝一下在\(JBB\)的指导之下,通过了此题!!!
刚开始看这个题的时候,不可做!!
数种类数的题最难了,不知道从哪里下手
然而可以发现一个性质,相同颜色的都在一起,就是连续的
所以说这个颜色种类数满足可减性,我们第二问就解决了,直接求出来\(LCA\)减一下就行了
那么我们看第一问,我们只关心颜色种类数
我们发现,我们更改的路径会对路径两侧的子树造成加一的贡献
而对路径上的数,会造成减的贡献
于是不知道咋想的,题解就想到了\(LCT\),这个确实很像吧
虚边表示颜色变化,实边表示相同的颜色,也就是说我们每一颗\(splay\)维护的都是一个相同颜色的连通块
那么我们再用一颗线段树维护每一个节点到根节点的颜色种类数
因为满足可减性,那就直接减
最大值,直接查子树最大值就行了
修改就是打通\(x\)到根的路径,\(access\)时更新线段树答案
断掉的实边表示增加答案,连上的实边表示减少答案
code
#include<bits/stdc++.h>
using namespace std;
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
int read(){
int s=0,t=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*t;
}
const int N=1e5+5;
int n,m;
int to[N*2],nxt[N*2],head[N],rp;
void add_edg(int x,int y){
to[++rp]=y;
nxt[rp]=head[x];
head[x]=rp;
}
int siz[N],son[N],dep[N],fa[N];
int top[N],dfn[N],dfm[N],cnt,idf[N];
struct XDS{
#define ls x<<1
#define rs x<<1|1
int mx[N*4],tag[N*4];
void pushup(int x){mx[x]=max(mx[ls],mx[rs]);}
void pushdown(int x){
mx[ls]+=tag[x];
tag[ls]+=tag[x];
mx[rs]+=tag[x];
tag[rs]+=tag[x];
tag[x]=0;
}
void ins(int x,int l,int r,int ql,int qr,int v){
if(ql>qr)return ;
if(ql<=l&&r<=qr){
mx[x]+=v;tag[x]+=v;
return ;
}
if(tag[x])pushdown(x);
int mid=l+r>>1;
if(ql<=mid)ins(ls,l,mid,ql,qr,v);
if(qr>mid)ins(rs,mid+1,r,ql,qr,v);
pushup(x);
}
int query(int x,int l,int r,int ql,int qr){
if(ql>qr)return 0;
if(ql<=l&&r<=qr)return mx[x];
if(tag[x])pushdown(x);
int mid=l+r>>1,ret=0;
if(ql<=mid)ret=max(ret,query(ls,l,mid,ql,qr));
if(qr>mid)ret=max(ret,query(rs,mid+1,r,ql,qr));
pushup(x);return ret;
}
#undef ls
#undef rs
}xds;
struct LCT{
struct POT{
int son[2],fa;
bool rev;
}tr[N];
bool nroot(int x){return tr[tr[x].fa].son[0]==x||tr[tr[x].fa].son[1]==x;}
int get(int x){return tr[tr[x].fa].son[1]==x;}
void rotate(int x){
int y=tr[x].fa,z=tr[y].fa;
int xpos=get(x),ypos=get(y);
if(nroot(y))tr[z].son[ypos]=x;
tr[x].fa=z;tr[y].fa=x;
tr[y].son[xpos]=tr[x].son[xpos^1];
tr[tr[x].son[xpos^1]].fa=y;
tr[x].son[xpos^1]=y;
}
void splay(int x){
while(nroot(x)){
int y=tr[x].fa,z=tr[y].fa;
int xpos=get(x),ypos=get(y);
if(nroot(y)){
if(xpos==ypos)rotate(y);
else rotate(x);
}
rotate(x);
}
}
int findrt(int x){
while(tr[x].son[0])x=tr[x].son[0];
return x;
}
void access(int x){
for(int y=0;x;y=x,x=tr[x].fa){
splay(x);
if(tr[x].son[1]){
int now=findrt(tr[x].son[1]);
xds.ins(1,1,n,dfn[now],dfm[now],1);
}
if(y){
int now=findrt(y);
xds.ins(1,1,n,dfn[now],dfm[now],-1);
}
tr[x].son[1]=y;
}
}
}lct;
void dfs_fi(int x,int f){
fa[x]=f;dep[x]=dep[f]+1;
lct.tr[x].fa=f;
siz[x]=1;son[x]=0;
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
if(y==fa[x])continue;
dfs_fi(y,x);
siz[x]+=siz[y];
if(!son[x]||siz[y]>siz[son[x]])son[x]=y;
}
}
void dfs_se(int x,int f){
top[x]=f;dfn[x]=++cnt;idf[cnt]=x;
if(son[x])dfs_se(son[x],f);
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
if(y==son[x]||y==fa[x])continue;
dfs_se(y,y);
}
dfm[x]=cnt;
xds.ins(1,1,n,dfn[x],dfm[x],1);
}
int LCA(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
signed main(){
n=read();m=read();
fo(i,1,n-1){
int x=read(),y=read();
add_edg(x,y);add_edg(y,x);
}
dfs_fi(1,0);dfs_se(1,1);
fo(i,1,m){
int tp=read(),x=read(),y;
if(tp==1)lct.access(x);
if(tp==2){
y=read();int lca=LCA(x,y);
printf("%d\n",xds.query(1,1,n,dfn[x],dfn[x])+xds.query(1,1,n,dfn[y],dfn[y])-2*xds.query(1,1,n,dfn[lca],dfn[lca])+1);
}
if(tp==3){
printf("%d\n",xds.query(1,1,n,dfn[x],dfm[x]));
}
}
}