1
给定一棵n个点的树,点有正点权,支持修改点权,查询带权直径。
普通的 DP 需要维护最长链和次长链,带修改的话不容易动态维护。因此我们采用另一种方法:找两次最远点。
LCT 维护的是:对于一棵 splay 的某个点,维护它 LCT 子树的答案,也就是对应的这条链的链顶的子树内,从这条链的端点出发的最长链(可能有点绕,很多 LCT 维护信息都是这么维护的)。为了维护这个信息,我们还需要维护每个虚儿子出发向(原树)子树的最长链,为了支持 access 用一个堆维护这些最长链,用堆顶就可以 push_up。
/*
lmax:从链顶出发的最长链
rmax:从链尾出发的最长链
sum:splay子树点权和
s:虚儿子最长链的堆
*/
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=100010;
struct node{
ll w;int x;
}lmax[N],rmax[N],Max[N];
struct edge{
int to,next;
}ed[N<<1];
multiset <node> s[N];
int fa[N],son[N][2],a[N],sz,head[N];
ll sum[N];
inline void add_edge(int from,int to)
{
ed[++sz].to=to;
ed[sz].next=head[from];
head[from]=sz;
}
node operator + (node a,ll x)
{
a.w+=x;
return a;
}
bool operator < (node a,node b)
{
if(a.w==b.w) return a.x<b.x;
return a.w<b.w;
}
inline int read()
{
char c=getchar();int x=0,flag=1;
while(!isdigit(c)){if(c=='-') flag=-1;c=getchar();}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*flag;
}
void dfs(int u,int ff)
{
for(int i=head[u];i;i=ed[i].next)
{
int v=ed[i].to;
if(v==ff) continue;
fa[v]=u;
dfs(v,u);
s[u].insert(lmax[v]);
}
lmax[u]=rmax[u]=*s[u].rbegin()+a[u];
Max[u]=*s[u].rbegin();
}
bool nroot(int x)
{
return son[fa[x]][0]==x||son[fa[x]][1]==x;
}
void push_up(int x)
{
int ls=son[x][0],rs=son[x][1];
lmax[x]=max(lmax[ls],max(lmax[rs],Max[x])+sum[ls]+a[x]);
rmax[x]=max(rmax[rs],max(rmax[ls],Max[x])+sum[rs]+a[x]);
sum[x]=sum[ls]+sum[rs]+a[x];
}
void rotate(int x)
{
int y=fa[x],z=fa[y],kind=x==son[y][0];
int tmp=nroot(y);
son[y][!kind]=son[x][kind];
fa[son[x][kind]]=y;
son[x][kind]=y;
fa[y]=x;
fa[x]=z;
if(tmp)
{
if(y==son[z][0]) son[z][0]=x;
else son[z][1]=x;
}
push_up(y);
}
void splay(int x)
{
while(nroot(x))
{
int y=fa[x],z=fa[y];
if(nroot(y))
{
if((x==son[y][0])^(y==son[z][0])) rotate(x);
else rotate(y);
}
rotate(x);
}
push_up(x);
}
void access(int x)
{
for(int y=0;x;x=fa[y=x])
{
splay(x);
if(son[x][1]) s[x].insert(lmax[son[x][1]]);
son[x][1]=y;
if(y) s[x].erase(s[x].find(lmax[y]));
Max[x]=*s[x].rbegin();
push_up(x);
}
}
int main()
{
int n=read(),m=read();
for(int i=1;i<=n;i++) a[i]=read(),s[i].insert((node){0,i});
for(int i=1;i<n;i++)
{
int u=read(),v=read();
add_edge(u,v),add_edge(v,u);
}
dfs(1,0);
while(m--)
{
int x=read(),y=read();
access(x),splay(x);
a[x]=y;
push_up(x);
x=lmax[x].x;
access(x);
splay(x);
cout<<rmax[x].w<<'\n';
}
return 0;
}
/*by DT_Kang*/
2
我们的 CPU 遭到攻击
维护一个森林,边有边权,支持 Link,Cut,反转一个点的颜色(黑白),查询某个联通块的所有黑点到某个点的距离之和。
这个题同样的,我们 splay 上的点维护 LCT 子树到链头/尾的答案(因为要支持 makeroot)。为了维护答案,还有维护黑点数量,边权之和。对于每个点,还要维护虚子树的黑点之和,以及虚子树答案。
/*
size:LCT子树黑点数
sizei:虚子树黑点数
ansi:虚儿子的答案
ansl:到链顶答案
sum:splay子树边权和
*/
#include<bits/stdc++.h>
#define ll long long
using namespace std;
unordered_map <ll,int> mp;
int son[400010][2],size[400010],sizei[400010],col[400010],val[400010],tot,fa[400010],tmp[400010],rev[400010];
ll ansl[400010],ansr[400010],ansi[400010],sum[400010];
inline int read()
{
char c=getchar();int x=0,flag=1;
while(!isdigit(c)){if(c=='-') flag=-1;c=getchar();}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*flag;
}
bool notroot(int x)
{
return (son[fa[x]][0]==x)||(son[fa[x]][1]==x);
}
void reverse(int x)
{
rev[x]^=1;
swap(son[x][0],son[x][1]);
swap(ansl[x],ansr[x]);
}
void push_up(int x)
{
int ls=son[x][0],rs=son[x][1];
sum[x]=sum[ls]+sum[rs]+val[x];
size[x]=size[ls]+size[rs]+col[x]+sizei[x];
ansl[x]=ansl[ls]+ansl[rs]+(sum[ls]+val[x])*(size[rs]+sizei[x]+col[x])+ansi[x];
ansr[x]=ansr[rs]+ansr[ls]+(sum[rs]+val[x])*(size[ls]+sizei[x]+col[x])+ansi[x];
}
void push_down(int x)
{
if(rev[x])
{
if(son[x][0]) reverse(son[x][0]);
if(son[x][1]) reverse(son[x][1]);
rev[x]=0;
}
}
void rotate(int x)
{
int y=fa[x],z=fa[y],kind= x==son[y][0];
int tmp=notroot(y);
son[y][!kind]=son[x][kind];
fa[son[x][kind]]=y;
fa[y]=x;
son[x][kind]=y;
fa[x]=z;
if(tmp)
{
if(y==son[z][0]) son[z][0]=x;
else son[z][1]=x;
}
push_up(y);
push_up(x);
}
void splay(int x)
{
int y=x,p=0;
while(notroot(y)) tmp[++p]=y,y=fa[y];
tmp[++p]=y;
while(p--) push_down(tmp[p+1]);
while(notroot(x))
{
int y=fa[x],z=fa[y];
if(notroot(y))
{
if((x==son[y][0])^(y==son[z][0])) rotate(x);
else rotate(y);
}
rotate(x);
}
}
void access(int x)
{
for(int y=0;x;x=fa[y=x])
{
splay(x);
int tmp=son[x][1];
sizei[x]+=size[tmp];
ansi[x]+=ansl[tmp];
son[x][1]=y;
sizei[x]-=size[y];
ansi[x]-=ansl[y];
push_up(x);
}
}
void makeroot(int x)
{
access(x),splay(x);
reverse(x);
}
void Link(int x,int y)
{
makeroot(x),makeroot(y);
fa[x]=y,sizei[y]+=size[x],ansi[y]+=ansl[x];
push_up(y);
}
void Cut(int x,int y)
{
makeroot(x),access(y),splay(y);
son[y][0]=fa[x]=0;
push_up(y);
}
void add_edge(int x,int y,int w)
{
tot++,val[tot]=w;
Link(x,tot),Link(tot,y);
mp[1000000ll*x+y]=mp[1000000ll*y+x]=tot;
}
int main()
{
int n=read(),m=read(),k=read();
tot=n;
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),w=read();
add_edge(u,v,w);
}
while(k--)
{
char s[5];
scanf("%s",s);
char c=s[0];
int x,y,w,tmp;
if(c=='L') x=read(),y=read(),w=read(),add_edge(x,y,w);
else if(c=='C') x=read(),y=read(),tmp=mp[1000000ll*x+y],Cut(x,tmp),Cut(tmp,y);
else if(c=='F') x=read(),access(x),splay(x),col[x]^=1,push_up(x);
else //if(c=='Q')
x=read(),makeroot(x),splay(x),cout<<ansl[x]<<'\n';
}
return 0;
}
/*by DT_Kang*/
3
Qtree7
维护三种操作:修改点的颜色(黑白),修改点的权值,查询 x 所在同色联通块的最大权值。
维护同色联通块的小技巧:我们把点的颜色放在它到父亲这条边上,同色边放在一棵 LCT 里。容易发现一个联通块只有根节点颜色不同。注意特判整棵树的根。
这个 LCT 是不需要换根的,注意 Link,Cut 的写法。
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fir first
#define sec second
#define ld long double
using namespace std;
const int N=200020,inf=1e9;
typedef pair <int,int> P;
struct edge {
int to,next;
}ed[N<<2];
int head[N],col[N],w[N],sz;
void add_edge(int from,int to)
{
ed[++sz].to=to;
ed[sz].next=head[from];
head[from]=sz;
}
int read()
{
int x=0;char c=getchar(),flag='+';
while(!isdigit(c)) flag=c,c=getchar();
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return flag=='-'?-x:x;
}
struct Heap
{
priority_queue <int> q1,q2;
void push(int x) {q1.push(x);}
void erase(int x) {q2.push(x);}
int top() {while(q2.size()&&q1.top()==q2.top()) q2.pop(),q1.pop();return q1.top();}
};
struct LCT
{
int son[N][2],fa[N],mx[N];
Heap si[N];
bool nroot(int x) {return son[fa[x]][0]==x||son[fa[x]][1]==x;}
void up(int x) {mx[x]=max(max(mx[son[x][0]],mx[son[x][1]]),max(w[x],si[x].top()));}
void rotate(int x)
{
int y=fa[x],z=fa[y],kind=x==son[y][0],tmp=nroot(y);
son[y][!kind]=son[x][kind];
fa[son[x][kind]]=y;
son[x][kind]=y;
fa[y]=x,fa[x]=z;
if(tmp)
{
if(y==son[z][0]) son[z][0]=x;
else son[z][1]=x;
}
up(y),up(x);
}
void splay(int x)
{
while(nroot(x))
{
int y=fa[x],z=fa[y];
if(nroot(y))
{
if(x==son[y][0]^y==son[z][0]) rotate(x);
else rotate(y);
}
rotate(x);
}
}
void access(int x)
{
for(int y=0;x;x=fa[y=x])
{
splay(x);
if(son[x][1]) si[x].push(mx[son[x][1]]);
if(y) si[x].erase(mx[y]);
son[x][1]=y,up(x);
}
}
void Cut(int x,int y)
{
if(!y) return;
access(x),splay(x);
son[x][0]=fa[son[x][0]]=0;
up(x);
}
void Link(int x,int y)
{
if(!y) return;
access(x),splay(x);
access(y),splay(y);
fa[x]=y;
si[y].push(mx[x]);
up(y);
}
int find(int x)
{
access(x),splay(x);
int now=x;
while(son[now][0]) now=son[now][0];
return now;
}
};
LCT T[2];
int fa[N];
void dfs(int u,int ff)
{
for(int i=head[u];i;i=ed[i].next)
{
int v=ed[i].to;
if(v==ff) continue;
T[col[v]].Link(v,u);
fa[v]=u;
dfs(v,u);
}
}
int main()
{
int n=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read();
add_edge(u,v),add_edge(v,u);
}
T[0].mx[0]=T[1].mx[0]=-inf;
for(int i=1;i<=n;i++) col[i]=read(),T[0].si[i].push(-inf),T[1].si[i].push(-inf),T[0].up(i),T[1].up(i);
for(int i=1;i<=n;i++) w[i]=read();
dfs(1,0);
int m=read();
for(int i=1;i<=m;i++)
{
int opt=read(),x=read(),c=col[x];
if(opt==0)
{
int tmp=T[c].find(x);
T[c].access(x);
T[c].splay(tmp);
if(c==col[tmp]) cout<<T[c].mx[tmp]<<'\n';
else cout<<T[c].mx[T[c].son[tmp][1]]<<'\n';
}
else if(opt==1)
{
T[c].Cut(x,fa[x]);
col[x]^=1,c^=1;
T[c].Link(x,fa[x]);
}
else
{
int val=read();
T[c].access(x),T[c].splay(x);
w[x]=val;
T[c].up(x);
}
}
return 0;
}
/*by DT_Kang*/