题解 QTREE7 - Query on a tree VII
题目描述
一棵树,每个点初始有个点权和颜色。
有以下操作:
-
询问所有 \(u,v\) 路径上的最大点权,要满足 \(u,v\) 路径上所有点的颜色都相同。
-
反转 \(u\) 的颜色。
-
把 \(u\) 的点权改成 \(w\) 。
具体思路
记白色为 \(0\),记黑色为 \(1\)。
LCT 维护的参数:
-
\(fa\):表示当前节点的父亲。
-
\(ch_{0/1}\):\(0\) 表示当前节点的左儿子,\(1\) 表示当前节点的右儿子。
-
\(val\):表示当前节点的权值。
-
\(col\):表示当前节点的颜色。
-
\(lmx\):表示以当前节点为根的子树内,从链头(最左端的点)出发最大连通块大小。
-
\(rmx\):表示以当前节点为根的子树内,从链尾(最右端的点)出发最大连通块大小。
-
\(tcol\):表示当前的实链颜色是否相同,都为白色 \(tcol=0\),都为黑色 \(tcol=1\),有不同颜色 \(tcol=2\)。
-
\(lcol\):表示当前链头(最左端的点)的颜色。
-
\(rcol\):表示当前链尾(最右端的点)的颜色。
-
\(lmax\):表示以当前节点为根的子树内,从链头(最左端的点)出发最大连通块内的最大值。
-
\(rmax\):表示以当前节点为根的子树内,从链尾(最右端的点)出发最大连通块内的最大值。
-
\(cnt_{0/1}\):表示当前节点的虚儿子的最大 \(0/1\) 连通块大小。
-
\(hfir_{0/1}\):表示当前节点的虚儿子的最大 \(0/1\) 连通块内 \(lmax\) 的最大值。
我们在当前 splay 中需要维护较多信息。
考虑 \(lmx\) 的转移:
-
\(lmx_{x}=lmx_{lc}\),继承左儿子的 \(lmx\)。
-
\(lmx_{x}+=cnt_{col_x,x}+1\) \((tcol_{lc} \ne 2,tcol_{lc}=col_x)\),就是链头可以走到 \(x\) 节点时可以加上虚儿子和它自己的贡献。
-
\(lmx_{x}+=lmx_{rc}\) \((tcol_{lc} \ne 2,tcol_{lc}=col_x,col_x=lcol_{rc})\),就是 \(x\) 节点可以走到 \(rc\) 所在子树的链头。
考虑 \(rmx\) 的转移:
-
\(rmx_{x}=rmx_{rc}\),继承右儿子的 \(rmx\)。
-
\(rmx_{x}+=cnt_{col_x,x}+1\) \((tcol_{rc} \ne 2,tcol_{rc}=col_x)\),就是链尾可以走到 \(x\) 节点时可以加上虚儿子和它自己的贡献。
-
\(rmx_{x}+=rmx_{lc}\) \((tcol_{rc} \ne 2,tcol_{rc}=col_x,col_x=rcol_{lc})\),就是 \(x\) 节点可以走到 \(lc\) 所在子树的链尾。
考虑 \(lcol\) 的转移:
-
\(lcol_x=col_x\) \((lc=0)\),没有左儿子自己就是链头。
-
\(lcol_x=lcol_lc\) \((lc \ne 0)\),继承左儿子的 \(lcol\)。
考虑 \(rcol\) 的转移:
-
\(rcol_x=col_x\) \((rc=0)\),没有右儿子自己就是链尾。
-
\(rcol_x=rcol_rc\) \((rc \ne 0)\),继承右儿子的 \(rcol\)。
考虑 \(tcol\) 的转移:
\(tcol_x=\begin{cases} 2 & tcol_{lc}=2 \\ 2 & tcol_rc=2 \\ 2 & tcol_{lc}\ne col_x \\ 2 & tcol_{rc}\ne col_x \\ col_x & otherwise \end{cases}\)
考虑 \(lmax\) 的转移:
-
\(lmax_{x}=lmax_{lc}\),继承左儿子的 \(lmax\)。
-
\(lmax_{x}=max(lmax_{x},hfir_{col_x,x},val_x)\) \((tcol_{lc} \ne 2,tcol_{lc}=col_x)\),就是链头可以走到 \(x\) 节点时可以加上虚儿子和它自己的贡献。
-
\(lmax_{x}=max(lmax_{x},lmax_{rc})\) \((tcol_{lc} \ne 2,tcol_{lc}=col_x,col_x=lcol_{rc})\),就是 \(x\) 节点可以走到 \(rc\) 所在子树的链头。
考虑 \(rmax\) 的转移:
-
\(rmax_{x}=rmax_{rc}\),继承右儿子的 \(rmax\)。
-
\(rmax_{x}=max(rmax_x,hfir_{col_x,x},val_x)\) \((tcol_{rc} \ne 2,tcol_{rc}=col_x)\),就是链尾可以走到 \(x\) 节点时可以加上虚儿子和它自己的贡献。
-
\(rmax_{x}=max(rmax_x,rmax_{lc})\) \((tcol_{rc} \ne 2,tcol_{rc}=col_x,col_x=rcol_{lc})\),就是 \(x\) 节点可以走到 \(lc\) 所在子树的链尾。
对于虚儿子的贡献部分,我们可以在建边和 access 的时候处理好,由于我们要维护虚儿子 \(lmax\) 的最大值,因此考虑用 multiset。
Code
#include<bits/stdc++.h>
#define fa(x) tr[x].fa
#define lc(x) tr[x].ch[0]
#define rc(x) tr[x].ch[1]
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')f=0;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return (!f)?-x:x;
}
const int N=2e5+5;
const int inf=0x7fffffff;
multiset<int>h[2][N];
struct trnode{
int val,col,fa,ch[2];
int cnt[2],hfir[2];
int lmx,rmx;
int tcol,lcol,rcol;
int lmax,rmax;
}tr[N];
int solve(multiset<int>&s){
if(s.size())return *s.rbegin();
else return -inf;
}
void work(int x){
tr[x].hfir[0]=solve(h[0][x]);
tr[x].hfir[1]=solve(h[1][x]);
}
bool notroot(int x){
return lc(fa(x))==x||rc(fa(x))==x;
}
void pushup(int x){
tr[x].lmx=tr[lc(x)].lmx;
tr[x].lmax=tr[lc(x)].lmax;
if((tr[lc(x)].tcol!=2&&tr[lc(x)].tcol==tr[x].col)||!lc(x)){
tr[x].lmx+=tr[x].cnt[tr[x].col]+1;
tr[x].lmax=max({tr[x].lmax,tr[x].hfir[tr[x].col],tr[x].val});
if(tr[x].col==tr[rc(x)].lcol){
tr[x].lmax=max(tr[x].lmax,tr[rc(x)].lmax);
tr[x].lmx+=tr[rc(x)].lmx;
}
}
tr[x].rmx=tr[rc(x)].rmx;
tr[x].rmax=tr[rc(x)].rmax;
if((tr[rc(x)].tcol!=2&&tr[rc(x)].tcol==tr[x].col)||!rc(x)){
tr[x].rmx+=tr[x].cnt[tr[x].col]+1;
tr[x].rmax=max({tr[x].rmax,tr[x].hfir[tr[x].col],tr[x].val});
if(tr[x].col==tr[lc(x)].rcol){
tr[x].rmax=max(tr[x].rmax,tr[lc(x)].rmax);
tr[x].rmx+=tr[lc(x)].rmx;
}
}
if(!lc(x))tr[x].lcol=tr[x].col;
else tr[x].lcol=tr[lc(x)].lcol;
if(!rc(x))tr[x].rcol=tr[x].col;
else tr[x].rcol=tr[rc(x)].rcol;
if(tr[lc(x)].tcol==2||tr[rc(x)].tcol==2)tr[x].tcol=2;
else if(lc(x)&&tr[lc(x)].tcol!=tr[x].col)tr[x].tcol=2;
else if(rc(x)&&tr[rc(x)].tcol!=tr[x].col)tr[x].tcol=2;
else tr[x].tcol=tr[x].col;
}
void rotate(int x){
int y=fa(x),z=fa(y);
int w=(rc(y)==x),v=tr[x].ch[1-w];
if(notroot(y)){
tr[z].ch[rc(z)==y]=x;
}
fa(x)=z;
fa(v)=y,tr[y].ch[w]=v;
fa(y)=x,tr[x].ch[1-w]=y;
pushup(y),pushup(x);
}
void splay(int x){
while(notroot(x)){
int y=fa(x),z=fa(y);
if(notroot(y)){
if((rc(z)==y)==(rc(y)==x))
rotate(y);
else rotate(x);
}
rotate(x);
}
}
void access(int x){
for(int y=0;x;y=x,x=fa(x)){
splay(x);
if(rc(x)){
tr[x].cnt[tr[rc(x)].lcol]+=tr[rc(x)].lmx;
h[tr[rc(x)].lcol][x].insert(tr[rc(x)].lmax);
}
if(y){
tr[x].cnt[tr[y].lcol]-=tr[y].lmx;
h[tr[y].lcol][x].erase(h[tr[y].lcol][x].find(tr[y].lmax));
}
work(x);
rc(x)=y;
pushup(x);
}
}
int query(int x){
access(x);
splay(x);
return tr[x].rmax;
}
void change(int x){
access(x);
splay(x);
tr[x].col^=1;
pushup(x);
}
void modify(int x,int c){
access(x);
splay(x);
tr[x].val=c;
pushup(x);
}
struct edge{
int x,y,pre;
}a[2*N];
int last[N],alen;
void ins(int x,int y){
a[++alen]=edge{x,y,last[x]};
last[x]=alen;
}
void dfs(int x,int fa){
for(int k=last[x];k;k=a[k].pre){
int y=a[k].y;
if(y==fa)continue;
fa(y)=x;
dfs(y,x);
tr[x].cnt[tr[y].lcol]+=tr[y].lmx;
h[tr[y].lcol][x].insert(tr[y].lmax);
}
work(x);
pushup(x);
}
int main(){
int n;n=read();
alen=1;memset(last,0,sizeof(last));
for(int i=1;i<n;i++){
int x,y;x=read(),y=read();
ins(x,y),ins(y,x);
}
for(int i=1;i<=n;i++){
int c;c=read();
tr[i].col=c;
tr[i].lmx=tr[i].rmx=1;
tr[i].lcol=tr[i].rcol=tr[i].tcol=c;
}
tr[0].lmax=tr[0].rmax=-inf;
for(int i=1;i<=n;i++){
int c;c=read();
tr[i].val=c;
tr[i].lmax=tr[i].rmax=c;
}
dfs(1,0);
int m;m=read();
for(int i=1;i<=m;i++){
int op,x,c;op=read();
if(op==0){
x=read();
printf("%d\n",query(x));
}
if(op==1){
x=read();
change(x);
}
if(op==2){
x=read(),c=read();
modify(x,c);
}
}
return 0;
}