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 处理树上问题能更好的思考和理解。

完结撒花~

posted @ 2022-04-01 21:53  violetctl39  阅读(65)  评论(0编辑  收藏  举报