Query on a tree
前言
这个系列的数据结构题太经典了,不得不补一下。
之前模拟赛考到过 Qtree4,直接莽了个假的点分治上去,人都傻了。
个人认为 Qtree 都不是很难。(除了 Qtree4)
Qtree1
题目描述
给定一棵 \(n\) 个节点的树,有两种操作:
CHANGE i t
把第 \(i\) 条边的边权变成 \(t\)QUERY a b
输出从 \(a\) 到 \(b\) 的路径上最大的边权。
题解
这个就是树剖或 LCT 的板子题了。树剖在线段树上维护最大值;LCT维护 splay 上最大值就行。
代码
有亿点点久远了,将就着看吧
这个是树剖版本的。
#include<bits/stdc++.h>
#define ls (pos<<1)
#define rs (pos<<1|1)
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m;
struct edge
{
int u,v,nxt,w;
}e[200005];
int head[100005],cnt=1;
void addedge(int u,int v,int w)
{
e[++cnt].v=v;
e[cnt].u=u;
e[cnt].w=w;
e[cnt].nxt=head[u];
head[u]=cnt;
}
struct tree
{
int val,top,fa,son,siz,id,dep;
}a[100005];
void dfs1(int x,int f)
{
a[x].fa=f;a[x].siz=1;
a[x].dep=a[f].dep+1;
int maxsiz=0;
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].v;
if(y==f)continue;
dfs1(y,x);a[x].siz+=a[y].siz;a[y].val=e[i].w;
if(a[y].siz>maxsiz)a[x].son=y,maxsiz=a[y].siz;
}
}
int tim=0;
int t[100005];
void dfs2(int x,int tp)
{
a[x].id=++tim;t[tim]=a[x].val;a[x].top=tp;
if(a[x].son)dfs2(a[x].son,tp);
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].v;
if(y==a[x].fa||y==a[x].son)continue;
dfs2(y,y);
}
}
int mx[5000005];
void build(int pos,int l,int r)
{
if(l==r){mx[pos]=t[l];return;}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
mx[pos]=max(mx[ls],mx[rs]);
}
void change(int pos,int l,int r,int x,int k)
{
if(l==r){mx[pos]=k;return;}
int mid=(l+r)>>1;
if(x<=mid)change(ls,l,mid,x,k);
else change(rs,mid+1,r,x,k);
mx[pos]=max(mx[ls],mx[rs]);
}
int query(int pos,int l,int r,int L,int R)
{
if(L<=l&&r<=R)return mx[pos];
int mid=(l+r)>>1,res=0;
if(L<=mid)res=max(res,query(ls,l,mid,L,R));
if(R>mid)res=max(res,query(rs,mid+1,r,L,R));
return res;
}
int solve(int x,int y)
{
int ans=0;
while(a[x].top!=a[y].top)
{
if(a[a[x].top].dep<a[a[y].top].dep)swap(x,y);
ans=max(ans,query(1,1,n,a[a[x].top].id,a[x].id));
x=a[a[x].top].fa;
}
if(x==y)return ans;
if(a[x].dep>a[y].dep)swap(x,y);
//cout<<a[x].id+1<<" "<<a[y].id<<endl;
ans=max(ans,query(1,1,n,a[x].id+1,a[y].id));
return ans;
}
int main()
{
n=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read(),w=read();
addedge(u,v,w),addedge(v,u,w);
}
dfs1(1,0);dfs2(1,1);
build(1,1,n);string s=" ";
while(cin>>s)
{
if(s=="DONE")break;
int x=read(),y=read();
if(s=="QUERY") printf("%d\n",solve(x,y));
else
{
int u=e[x<<1].u,v=e[x<<1].v;
if(a[u].dep<a[v].dep)swap(u,v);
change(1,1,n,a[u].id,y);
}
}
return 0;
}
Qtree2
题目描述
给定一棵n个点的树,边具有边权。要求作以下操作:
-
DIST a b
询问点 \(a\) 至点 \(b\) 路径上的边权之和 -
KTH a b k
询问点 \(a\) 至点 \(b\) 有向路径上的第 \(k\) 个点的编号
题解
很水,第一个很好处理,维护 \(sum\) 就行。至于第二个就有点麻烦,如果是用树剖做的话,得分两种情况求解,先看 \((u,\text{LCA}(u,v))\),如果这上面的点数大于 \(k\),那就在 \((\text{LCA}(u,v),v)\) 上找,个人认为不是很好写;如果用 LCT 来做就很方便了,因为把 \((u,v)\) 这条路径拉出来,在 splay 上可以直接找 kth。
两种方法时间都是 \(O(n\log n)\) 的。
代码
LCT版本,好写好调。(只是常数大了亿点点)
#include<bits/stdc++.h>
#define pc(x) putchar(x)
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void write(int x)
{
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+48);
}
int T,n;char s[10];
int val[20005],sum[20005],siz[20005],rev[20005],ch[20005][2],fa[20005];
inline bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
inline void pushrev(int x){swap(ch[x][0],ch[x][1]);rev[x]^=1;}
inline void pushup(int x){sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+val[x];siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1;}
inline void pushdown(int x)
{
if(rev[x])
{
if(ch[x][0])pushrev(ch[x][0]);
if(ch[x][1])pushrev(ch[x][1]);
}rev[x]=0;
}
inline void update(int x)
{
if(!isroot(x))update(fa[x]);
pushdown(x);
}
inline void rotate(int x)
{
int y=fa[x],z=fa[y];
int k=ch[y][1]==x,w=ch[x][k^1];
if(!isroot(y))ch[z][ch[z][1]==y]=x;
ch[x][k^1]=y;ch[y][k]=w;
if(w){fa[w]=y;}fa[y]=x;fa[x]=z;
pushup(y);
}
inline void splay(int x)
{
update(x);
while(!isroot(x))
{
int y=fa[x],z=fa[y];
if(!isroot(y))rotate((ch[y][0]==x)^(ch[z][0]==y)?x:y);
rotate(x);
}pushup(x);
}
inline void access(int x)
{
for(int y=0;x;x=fa[y=x])
splay(x),ch[x][1]=y,pushup(x);
}
inline void makeroot(int x){access(x);splay(x);pushrev(x);}
inline void split(int x,int y){makeroot(x);access(y);splay(y);}
inline void link(int x,int y){makeroot(x);fa[x]=y;}
inline int find(int x,int k)
{
while(1)
{
pushdown(x);
if(siz[ch[x][0]]>=k)x=ch[x][0];
else if(siz[ch[x][0]]+1==k)return x;
else k-=siz[ch[x][0]]+1,x=ch[x][1];
}return 0;
}
inline void init()
{
fill(val+1,val+2*n,0);fill(sum+1,sum+2*n,0);
fill(siz+1,siz+2*n,0);fill(rev+1,rev+2*n,0);
fill(fa+1,fa+2*n,0);
for(int i=1;i<2*n;++i)ch[i][0]=ch[i][1]=0;
}
int main()
{
T=read();
while(T-->0)
{
n=read();init();
for(int i=1;i<n;++i)
{
int u=read(),v=read(),w=read();
val[n+i]=w;link(u,n+i);link(v,n+i);
}
while(1)
{
scanf("%s",s+1);if(s[2]=='O')break;
int u=read(),v=read();split(u,v);
if(s[1]=='D'){write(sum[v]),pc('\n');}
else write(find(v,2*read()-1)),pc('\n');
}
}return 0;
}
Qtree3
题目描述
给出 \(n\) 个点的一棵树(\(n-1\) 条边),节点有白有黑,初始全为白。有两种操作:
-
0 i
改变某点的颜色(原来是黑的变白,原来是白的变黑) -
1 v
询问 \(1\) 到 \(v\) 的路径上的第一个黑点,若无,输出 \(-1\)
题解
接下来就不想讲树剖做法了。(只是懒)
一如既往的 LCT 水题。第一个操作直接 makeroot(x)
然后修改;第二个操作维护一个 \(sum[x]\),为在 splay 中 \(x\) 的子树有多少个黑点,因为左儿子深度更浅,又是从 \(1\) 出发求最近的黑点,所以有黑点就往左儿子走就行了。
时间复杂度 \(O(n\log n)\)。
代码
#include<bits/stdc++.h>
#define pc(x) putchar(x)
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void write(int x)
{
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+48);
}
int n,q;
int val[100005],sum[100005],rev[100005],ch[100005][2],fa[100005];
inline bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
inline void pushrev(int x){swap(ch[x][0],ch[x][1]);rev[x]^=1;}
inline void pushup(int x){sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+val[x];}
inline void pushdown(int x)
{
if(rev[x])
{
if(ch[x][0])pushrev(ch[x][0]);
if(ch[x][1])pushrev(ch[x][1]);
}rev[x]=0;
}
inline void update(int x)
{
if(!isroot(x))update(fa[x]);
pushdown(x);
}
inline void rotate(int x)
{
int y=fa[x],z=fa[y];
int k=ch[y][1]==x,w=ch[x][k^1];
if(!isroot(y))ch[z][ch[z][1]==y]=x;
ch[x][k^1]=y;ch[y][k]=w;
if(w){fa[w]=y;}fa[y]=x;fa[x]=z;
pushup(y);
}
inline void splay(int x)
{
update(x);
while(!isroot(x))
{
int y=fa[x],z=fa[y];
if(!isroot(y))rotate((ch[y][0]==x)^(ch[z][0]==y)?x:y);
rotate(x);
}pushup(x);
}
inline void access(int x)
{
for(int y=0;x;x=fa[y=x])
splay(x),ch[x][1]=y,pushup(x);
}
inline void makeroot(int x){access(x);splay(x);pushrev(x);}
inline void split(int x,int y){makeroot(x);access(y);splay(y);}
inline void link(int x,int y){makeroot(x);fa[x]=y;}
inline int find(int x)
{
if(!sum[x])return -1;
while(1)
{
pushdown(x);
if(sum[ch[x][0]])x=ch[x][0];
else if(val[x])return x;
else x=ch[x][1];
}return 0;
}
int main()
{
n=read(),q=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
link(u,v);
}
for(int i=1;i<=q;++i)
{
int op=read(),x=read();
if(!op){makeroot(x);val[x]^=1;pushup(x);}
else split(1,x),write(find(x)),pc('\n');
}return 0;
}
Qtree4
题目描述
给出一棵边带权的节点数量为 \(n\) 的树,初始树上所有节点都是白色。有两种操作:
-
C x
改变节点 \(x\) 的颜色,即白变黑,黑变白 -
A
询问树中最远的两个白色节点的距离,这两个白色节点可以重合(此时距离为 \(0\))。
题解
LCT做法不会,看了大佬们的题解才搞懂的。
以下部分来自原题解。
\(len[x]\):因为是边权,所以要下放到点。
\(lmax[x]\):在 splay 中以 \(x\) 为根的以第一个点为端点的最长链。
\(rmax[x]\):在 splay 中以 \(x\) 为根的以最后一个点为端点的最长链( 注:这样可以保证之后合并时是连续的,因为 \(x\) 的右儿子的第一个端点在原树上必定挨着 \(x\),左儿子的最后一个也是挨着 \(x\) 的并且是 \(x\) 的父亲,因为是边权下放到点,后面会出现左右儿子不同的地方。
\(far[x]\):代表splay中以 \(x\) 为根的最远白色点对。
\(sum[x]\):代表splay中以 \(x\) 为根的所有点的权值和,求 \(lmax,rmax\) 要用。
\(id[x]\):点亮就是 \(1\),否则是 \(0\)。
\(h1[x]\):所有虚儿子中的 \(lmax\) 最大值(用 \(multiset\) 存,\(insert,erase\) 都更新一下,是为了降常数才记下来的)。
\(h2[x]\):所有虚儿子中的 \(lmax\) 次大值。
\(p1[x]\):所有虚儿子中 \(far[y]\) 的最大值(另一个 \(multiset\) 存)。
合并的过程可以手动模拟一下下,理解会更深刻。
子树最值不能直接维护,我们可以用 multiset 维护虚子树信息,单次都是 \(O(\log n)\)。
总时间复杂度是 \(O(n\log ^2n)\)。
这种做法个人其实不是很喜欢,下放边权会让代码难写难调,等什么时候想出来化边为点的做法再更新。
代码
#include<bits/stdc++.h>
#define pc(x) putchar(x)
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void write(int x)
{
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+48);
}
const int inf=1e9;
int T,n,q,ans;char s[15];
struct edge{int v,w;};
vector<edge>v[100005];
int id[100005],val[100005],sum[100005],rev[100005];
int ch[100005][2],fa[100005],lmax[100005],rmax[100005],far[100005];
int h1[100005],h2[100005],p1[100005];
multiset<int>h[100005],p[100005];
void gethp(int x)
{
h1[x]=h2[x]=p1[x]=-inf;
if(h[x].size())
{
auto it=--h[x].end();h1[x]=*it;
if(h[x].size()>1)h2[x]=*--it;
}if(p[x].size())p1[x]=*--p[x].end();
}
inline bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
inline void pushrev(int x){swap(ch[x][0],ch[x][1]);rev[x]^=1;}
inline void pushup(int x)
{
sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+val[x];
int add=id[x]?max(0,h1[x]):h1[x];
int L=max(add,rmax[ch[x][0]]+val[x]);
int R=max(add,lmax[ch[x][1]]);
lmax[x]=max(lmax[ch[x][0]],sum[ch[x][0]]+val[x]+R);
rmax[x]=max(rmax[ch[x][1]],sum[ch[x][1]]+L);
far[x]=max(rmax[ch[x][0]]+val[x]+R,lmax[ch[x][1]]+L);
far[x]=max(far[x],max(far[ch[x][0]],far[ch[x][1]]));
far[x]=max(far[x],h1[x]+h2[x]);far[x]=max(far[x],p1[x]);
if(id[x])far[x]=max(0,max(far[x],h1[x]));
}
inline void pushdown(int x)
{
if(rev[x])
{
if(ch[x][0])pushrev(ch[x][0]);
if(ch[x][1])pushrev(ch[x][1]);
}rev[x]=0;
}
inline void update(int x)
{
if(!isroot(x))update(fa[x]);
pushdown(x);
}
inline void rotate(int x)
{
int y=fa[x],z=fa[y];
int k=ch[y][1]==x,w=ch[x][k^1];
if(!isroot(y))ch[z][ch[z][1]==y]=x;
ch[x][k^1]=y;ch[y][k]=w;
if(w){fa[w]=y;}fa[y]=x;fa[x]=z;
pushup(y);
}
inline void splay(int x)
{
update(x);
while(!isroot(x))
{
int y=fa[x],z=fa[y];
if(!isroot(y))rotate((ch[y][0]==x)^(ch[z][0]==y)?x:y);
rotate(x);
}pushup(x);
}
inline void access(int x)
{
for(int y=0;x;x=fa[y=x])
{
splay(x);
if(ch[x][1])h[x].insert(lmax[ch[x][1]]),p[x].insert(far[ch[x][1]]);
if(y)h[x].erase(h[x].find(lmax[y])),p[x].erase(p[x].find(far[y]));
gethp(x);ch[x][1]=y;pushup(x);
}
}
void dfs(int x)
{
for(edge e:v[x])
{
if(e.v==fa[x])continue;
fa[e.v]=x;val[e.v]=e.w;dfs(e.v);
h[x].insert(lmax[e.v]);p[x].insert(far[e.v]);
}gethp(x);pushup(x);
}
int main()
{
n=read();
for(int i=0;i<=n;++i)lmax[i]=rmax[i]=far[i]=-inf;
for(int i=1;i<=n;++i)id[i]=1;
for(int i=1;i<n;++i)
{
int x=read(),y=read(),w=read();
v[x].push_back((edge){y,w});
v[y].push_back((edge){x,w});
}dfs(1);ans=far[1];T=read();
while(T-->0)
{
scanf("%s",s+1);
if(s[1]=='A')
{
if(ans>=0)write(ans),pc('\n');
else puts("They have disappeared.");
}
else
{
int x=read();access(x);splay(x);
id[x]^=1;pushup(x);ans=far[x];
}
}return 0;
}
Qtree5
题目描述
你被给定一棵n个点的树,点从1到n编号。每个点可能有两种颜色:黑或白。我们定义 dist(a,b)
为点 \(a\) 至点 \(b\) 路径上的边个数。一开始所有的点都是黑色的。要求作以下操作:
-
0 i
将点i的颜色反转(黑变白,白变黑) -
1 v
询问dist(u,v)
的最小值。\(u\) 点必须为白色(\(u\) 与 \(v\) 可以相同),显然如果 \(v\) 是白点,查询得到的值一定是 \(0\)。
特别地,如果询问时树上没有白点,输出 \(-1\)。
题解
************************。
这题调了一下午加四分之一个晚自习,就因为 multiset 不存在元素而删除直接 RE 了。
究极原因是 (pushup
漏写了一句话
首先 \(dist(u,v)=dep[u]+dep[v]-2\times dep[\text{LCA}(u,v)]\),其中 \(dep[v]\) 我们已知,所以求距离最小值相当于要最小化 \(dep[u]-2\times dep[\text{LCA}(u,v)]\)。注意到 \(\text{LCA}(u,v)\) 一定是 \(v\) 的祖先,所以我们可以在 \(v\) 的祖先上维护子树中最小的 \(dep[u]-2\times dep[\text{LCA}(u,v)]\),相当于在 LCT 中维护虚子树的白点 \(dep[u]\) 最小值,记 \(val[fa]=\min\{dep[u]-2\times dep[\text{LCA}(u,v)]\}\),那么答案就是 \(ans=\min\{val[fa]+dep[v]\}\)。这个 \(val[fa]\) 可以在 splay 中维护,每次询问 splay(v)
,\(val[v]\) 就是最小的 \(val[fa]\)。
至于修改,直接 splay(v)
,然后修改颜色再 pushup(v)
就行了。
总时间复杂度 \(O(n\log^2 n)\)。
代码
#include<bits/stdc++.h>
#define pc(x) putchar(x)
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void write(int x)
{
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+48);
}
const int inf=1e9;
int n,q;vector<int>e[100005];
int id[100005],dep[100005],val[100005];
int rev[100005],ch[100005][2],fa[100005],mndep[100005];
multiset<int>vir[100005];
inline bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
inline void pushrev(int x){swap(ch[x][0],ch[x][1]);rev[x]^=1;}
inline void pushup(int x)
{
mndep[x]=min(id[x]?dep[x]:inf,vir[x].size()?(*vir[x].begin()):inf);
if(mndep[x]!=inf)val[x]=mndep[x]-2*dep[x]; else val[x]=inf;
if(ch[x][0])val[x]=min(val[x],val[ch[x][0]]),mndep[x]=min(mndep[x],mndep[ch[x][0]]);
if(ch[x][1])val[x]=min(val[x],val[ch[x][1]]),mndep[x]=min(mndep[x],mndep[ch[x][1]]);
}
inline void pushdown(int x)
{
if(rev[x])
{
if(ch[x][0])pushrev(ch[x][0]);
if(ch[x][1])pushrev(ch[x][1]);
}rev[x]=0;
}
inline void update(int x)
{
if(!isroot(x))update(fa[x]);
pushdown(x);
}
inline void rotate(int x)
{
int y=fa[x],z=fa[y];
int k=ch[y][1]==x,w=ch[x][k^1];
if(!isroot(y))ch[z][ch[z][1]==y]=x;
ch[x][k^1]=y;ch[y][k]=w;
if(w){fa[w]=y;}fa[y]=x;fa[x]=z;
pushup(y);
}
inline void splay(int x)
{
update(x);
while(!isroot(x))
{
int y=fa[x],z=fa[y];
if(!isroot(y))rotate((ch[y][0]==x)^(ch[z][0]==y)?x:y);
rotate(x);
}pushup(x);
}
inline void access(int x)
{
for(int y=0;x;x=fa[y=x])
{
splay(x);
if(ch[x][1])vir[x].insert(mndep[ch[x][1]]);
if(y)vir[x].erase(vir[x].find(mndep[y]));
ch[x][1]=y,pushup(x);
}
}
void dfs(int x)
{
for(int y:e[x])
{
if(y==fa[x])continue;
fa[y]=x;dep[y]=dep[x]+1;
dfs(y);vir[x].insert(mndep[y]);
}pushup(x);
}
int main()
{
n=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
e[u].push_back(v);
e[v].push_back(u);
}dep[1]=1;dfs(1);q=read();
for(int i=1;i<=q;++i)
{
int op=read(),x=read();
if(!op){access(x);splay(x);id[x]^=1;pushup(x);}
else{access(x);splay(x);write(val[x]==inf?-1:val[x]+dep[x]),pc('\n');}
}return 0;
}
Qtree6
题目描述
给你一棵 \(n\) 个点的树,编号 \(1\sim n\)。每个点可以是黑色,可以是白色。初始时所有点都是黑色。下面有两种操作:
0 u
询问有多少个节点 \(v\) 满足路径 \(u\) 到 \(v\) 上所有节点(包括 \(u\))都拥有相同的颜色。1 u
翻转 \(u\) 的颜色。
题解
连通块问题,用两个 LCT 分别维护同色连通块,再维护虚子树大小即可。问题在于修改,更改一个点的颜色就要断掉周围的所有边,如果是一个菊花图就卡掉了。
实际上我们只需要断掉当前颜色的父边,连上另一种颜色的父边,此时这个点是当前颜色子树的根,我们可以把它看作一个虚点,统计答案时不用管它,直接统计儿子的信息;至于另一种颜色,因为我们连上了父边,所以统计答案时会统计到自己。这样每次操作的复杂度是正确的 \(O(\log n)\)。
代码
#include<bits/stdc++.h>
#define pc(x) putchar(x)
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void write(int x)
{
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+48);
}
int n,m;
vector<int>e[100005];
int col[100005],f[100005];
struct LCT
{
int fa[100005],siz[100005],vir[100005],ch[100005][2];
inline bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
inline void pushup(int x){siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+vir[x]+1;}
inline void rotate(int x)
{
int y=fa[x],z=fa[y];
int k=ch[y][1]==x,w=ch[x][k^1];
if(!isroot(y))ch[z][ch[z][1]==y]=x;
ch[x][k^1]=y;ch[y][k]=w;
if(w){fa[w]=y;}fa[y]=x;fa[x]=z;
pushup(y);
}
inline void splay(int x)
{
while(!isroot(x))
{
int y=fa[x],z=fa[y];
if(!isroot(y))rotate((ch[y][0]==x)^(ch[z][0]==y)?x:y);
rotate(x);
}pushup(x);
}
inline void access(int x)
{
for(int y=0;x;x=fa[y=x])
{
splay(x);vir[x]+=siz[ch[x][1]];
vir[x]-=siz[y];ch[x][1]=y;pushup(x);
}
}
inline int findroot(int x)
{
access(x);splay(x);
while(ch[x][0])x=ch[x][0];
splay(x);return x;
}
inline void link(int x)
{
splay(x);int y=fa[x]=f[x];
access(y);splay(y);vir[y]+=siz[x];
pushup(y);
}
inline void cut(int x)
{
access(x);splay(x);
ch[x][0]=fa[ch[x][0]]=0;
pushup(x);
}
}tr[2];
void dfs(int x)
{
for(int y:e[x])if(y!=f[x])
f[y]=x,dfs(y),tr[0].link(y);
}
int main()
{
n=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
e[u].push_back(v);
e[v].push_back(u);
}dfs(1);f[1]=n+1;tr[0].link(1);m=read();
for(int i=1;i<=m;++i)
{
int op=read(),x=read();
if(op)tr[col[x]].cut(x),tr[col[x]^=1].link(x);
else{int y=tr[col[x]].findroot(x);write(tr[col[x]].siz[tr[col[x]].ch[y][1]]),pc('\n');}
}return 0;
}
Qtree7
题目描述
一棵树,每个点初始有个点权和颜色。有三种操作:
-
0 u
询问所有 \(u,v\) 路径上的最大点权,要满足 \(u,v\) 路径上所有点的颜色都相同。 -
1 u
反转 \(u\) 的颜色。 -
2 u w
把 \(u\) 的点权改成 \(w\)。
题解
会了 Qtree6,这题就很水了。和上一题一样,要维护同色连通块以及虚子树最大值。这个就相当于结合了前面几个题目的做法,维护同色连通块就开 \(2\) 个 LCT,维护虚子树最值就开 multiset。没什么难度。
代码
#include<bits/stdc++.h>
#define pc(x) putchar(x)
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void write(int x)
{
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+48);
}
const int inf=1e9;
int n,m;
vector<int>e[100005];
int col[100005],f[100005];
int val[100005];
struct LCT
{
int fa[100005],mx[100005],ch[100005][2];
multiset<int>vir[100005];LCT(){mx[0]=-inf;}
inline bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
inline void pushup(int x)
{
mx[x]=val[x];if(vir[x].size())mx[x]=max(mx[x],*vir[x].rbegin());
mx[x]=max(mx[x],max(mx[ch[x][0]],mx[ch[x][1]]));
}
inline void rotate(int x)
{
int y=fa[x],z=fa[y];
int k=ch[y][1]==x,w=ch[x][k^1];
if(!isroot(y))ch[z][ch[z][1]==y]=x;
ch[x][k^1]=y;ch[y][k]=w;
if(w){fa[w]=y;}fa[y]=x;fa[x]=z;
pushup(y);
}
inline void splay(int x)
{
while(!isroot(x))
{
int y=fa[x],z=fa[y];
if(!isroot(y))rotate((ch[y][0]==x)^(ch[z][0]==y)?x:y);
rotate(x);
}pushup(x);
}
inline void access(int x)
{
for(int y=0;x;x=fa[y=x])
{
splay(x);vir[x].insert(mx[ch[x][1]]);
vir[x].erase(vir[x].find(mx[y]));
ch[x][1]=y;pushup(x);
}
}
inline int findroot(int x)
{
access(x);splay(x);
while(ch[x][0])x=ch[x][0];
splay(x);return x;
}
inline void link(int x)
{
splay(x);int y=fa[x]=f[x];
access(y);splay(y);
vir[y].insert(mx[x]);
pushup(y);
}
inline void cut(int x)
{
access(x);splay(x);
ch[x][0]=fa[ch[x][0]]=0;
pushup(x);
}
inline void modify(int x,int w)
{
access(x);splay(x);
val[x]=w;pushup(x);
}
}tr[2];
void dfs(int x)
{
for(int y:e[x])if(y!=f[x])
f[y]=x,dfs(y),tr[0].link(y);
}
int main()
{
n=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
e[u].push_back(v);
e[v].push_back(u);
}
for(int i=1;i<=n;++i)col[i]=read();
for(int i=1;i<=n;++i)val[i]=read();
dfs(1);f[1]=n+1;tr[0].link(1);m=read();
for(int i=1;i<=n;++i)if(col[i])tr[0].cut(i),tr[1].link(i);
for(int i=1;i<=m;++i)
{
int op=read(),x=read();
if(op==1)tr[col[x]].cut(x),tr[col[x]^=1].link(x);
else if(op==2){tr[col[x]].modify(x,read());}
else{int y=tr[col[x]].findroot(x);write(tr[col[x]].mx[tr[col[x]].ch[y][1]]),pc('\n');}
}return 0;
}
后记
这几道题做了好几天(中途鸽了好久),做种题没见过就真不会,做过一遍这一类型就都掌握了。做的意义还是蛮大的,对 LCT 处理树上问题能更好的思考和理解。
完结撒花~