spoj树上查询系列题:QTREE

warning: 本文不同题之间的编写时间有一年左右的间隔,因此码风不统一,见谅。

QTREE

树有边权
CHANGE:将第x条边的边权改为y
QUERY:询问两点之间路径上的最大边权。

为了练习树链剖分,就用树链剖分做了。还是不难的,链剖后就是线段树单点修改和区间查询最大值了。注意一点,我将每个边权赋给了这条边连接的两点中深度较大的那个,所以线段树操作的时候要注意不要操作lca(代码注释部分)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int read() {
	int q=0,w=1;char ch=' ';
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') w=-1,ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q*w;
}
const int N=10005,inf=0x3f3f3f3f;
int T,n,tot,now;
int h[N],ne[N<<1],to[N<<1],w[N<<1],u[N],v[N];
int pos[N],dep[N],f[N],top[N],sz[N],mx[N<<2],a[N];
void adde(int x,int y,int z)
{to[++tot]=y,ne[tot]=h[x],h[x]=tot,w[tot]=z;}
void dfs1(int x) {
	dep[x]=dep[f[x]]+1,sz[x]=1;
	for(int i=h[x];i;i=ne[i])
		if(to[i]!=f[x]) f[to[i]]=x,dfs1(to[i]),sz[x]+=sz[to[i]];
}
void dfs2(int x) {
	pos[x]=++now;int bj=0,mm=0;
	for(int i=h[x];i;i=ne[i])
		if(to[i]!=f[x]&&sz[to[i]]>mm) mm=sz[to[i]],bj=to[i];
	if(!bj) return;
	top[bj]=top[x],dfs2(bj);
	for(int i=h[x];i;i=ne[i])
		if(to[i]!=f[x]&&to[i]!=bj) top[to[i]]=to[i],dfs2(to[i]);
}
void up(int i) {mx[i]=max(mx[i<<1],mx[(i<<1)|1]);}
void build(int s,int t,int i) {
	if(s==t) {mx[i]=a[s];return;}
	int mid=(s+t)>>1;
	build(s,mid,i<<1),build(mid+1,t,(i<<1)|1);
	up(i);
}
void chan(int x,int s,int t,int i,int num) {
	if(s==t) {mx[i]=num;return;}
	int mid=(s+t)>>1;
	if(x<=mid) chan(x,s,mid,i<<1,num);
	else chan(x,mid+1,t,(i<<1)|1,num);
	up(i);
}
int query(int l,int r,int s,int t,int i) {
	if(l<=s&&t<=r) return mx[i];
	int mid=(s+t)>>1,re=-inf;
	if(l<=mid) re=query(l,r,s,mid,i<<1);
	if(mid+1<=r) re=max(re,query(l,r,mid+1,t,(i<<1)|1));
	return re;
}
int main()
{
	int x,y,z;char ch[10];
	T=read();
	while(T--) {
		n=read();
		tot=0;for(int i=1;i<=n;++i) h[i]=0;
		for(int i=1;i<n;++i) {
			u[i]=read(),v[i]=read(),z=read();
			adde(u[i],v[i],z),adde(v[i],u[i],z);
		}
		now=0,top[1]=1,dfs1(1),dfs2(1);
		for(int i=1;i<n;++i) {
			if(dep[u[i]]>dep[v[i]]) swap(u[i],v[i]);
			a[pos[v[i]]]=w[i<<1];
		}
		build(1,n,1);
		while("niconiconi") {
			scanf("%s",ch);
			if(ch[0]=='D') break;
			if(ch[0]=='C') x=read(),y=read(),chan(pos[v[x]],1,n,1,y);
			else {
				int ans=-inf;x=read(),y=read();
				while(top[x]!=top[y]) {
					if(dep[top[x]]<dep[top[y]]) swap(x,y);
					ans=max(ans,query(pos[top[x]],pos[x],1,n,1));
					x=f[top[x]];
				}
				if(pos[x]>pos[y]) swap(x,y);
				if(pos[x]<pos[y]) ans=max(ans,query(pos[x]+1,pos[y],1,n,1));
				//pos[x]+1:注意不要操作lca
				printf("%d\n",ans);
			}
		}
		puts("");
	}
	return 0;
}

QTREE2

树有边权。
DIST:询问两点之间的距离
KTH:询问两点之间的路径上的第k个点

这应该是一道倍增lca的水题吧,对于第二个询问,判断一下是起点或终点的第几个父亲,然后用倍增思想找到它即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int read() {
	int q=0,w=1;char ch=' ';
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') w=-1,ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q*w;
}
const int N=10005;
int T,n,tot;
int h[N],to[N<<1],ne[N<<1],w[N<<1],f[N][16],dis[N],dep[N],bin[16];
void add(int x,int y,int z)
{to[++tot]=y,ne[tot]=h[x],h[x]=tot,w[tot]=z;}
void dfs(int x,int pre,int lon) {
	f[x][0]=pre,dep[x]=dep[pre]+1,dis[x]=dis[pre]+lon;
	for(int i=1;i<=15;++i) f[x][i]=f[f[x][i-1]][i-1];
	for(int i=h[x];i;i=ne[i])
		if(to[i]!=pre) dfs(to[i],x,w[i]);
}
int lca(int x,int y) {
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=15;i>=0;--i)
		if(dep[f[x][i]]>=dep[y]) x=f[x][i];
	if(x==y) return x;
	for(int i=15;i>=0;--i)
		if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	return f[x][0];
}
int getf(int x,int num) {
	for(int i=0;i<=15;++i)
		if(num&bin[i]) x=f[x][i];
	return x;
}
int main()
{
	int x,y,z,o;char ch[10];
	T=read();
	bin[0]=1;for(int i=1;i<=15;++i) bin[i]=bin[i-1]<<1;
	while(T--) {
		n=read();
		tot=0;for(int i=1;i<=n;++i) h[i]=0;
		for(int i=1;i<n;++i)
			x=read(),y=read(),z=read(),add(x,y,z),add(y,x,z);
		dep[1]=1,dfs(1,0,0);
		while("niconiconi") {
			scanf("%s",ch);
			if(ch[0]=='D'&&ch[1]=='O') break;
			if(ch[0]=='D')
				x=read(),y=read(),printf("%d\n",dis[x]+dis[y]-dis[lca(x,y)]*2);
			else {
				x=read(),y=read(),z=read(),o=lca(x,y);
				int ds=dep[x]+dep[y]-dep[o]*2+1,kl=dep[x]-dep[o]+1;
				if(z<=kl) printf("%d\n",getf(x,z-1));
				else printf("%d\n",getf(y,ds-z));
			}
		}
		puts("");
	}
	return 0;
}

QTREE3

点分为黑点和白点,操作是将一个点变色,询问是询问1到x的路径上的第一个黑点

我用的是lct,同时要维护一个sum,即x所在splay子树中黑点的个数(或者维护有没有黑点)

修改的话就把x旋转到其所在的splay的根,然后修改即可。

查询就执行evert(1),access(x),然后把splay旋转到根,在splay上查询深度最小的黑点即可。

#include<bits/stdc++.h>
using namespace std;
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int N=100005;
int n,q,top;
int f[N],sum[N],a[N],son[N][2],rev[N],s[N];
int is(int x) {return x==son[f[x]][1];}
int isroot(int x) {return x!=son[f[x]][0]&&x!=son[f[x]][1];}
void up(int x) {sum[x]=sum[son[x][0]]+sum[son[x][1]]+a[x];}
void pd(int x) {
	if(!rev[x]) return;
	if(son[x][0]) rev[son[x][0]]^=1;
	if(son[x][1]) rev[son[x][1]]^=1;
	swap(son[x][0],son[x][1]),rev[x]=0;
}
void spin(int x) {
	int fa=f[x],g=f[fa],t=is(x);
	if(!isroot(fa)) son[g][is(fa)]=x;
	f[fa]=x,f[x]=g,f[son[x][t^1]]=fa;
	son[fa][t]=son[x][t^1],son[x][t^1]=fa;
	up(fa),up(x);
}
void splay(int x) {
	s[++top]=x;for(int i=x;!isroot(i);i=f[i]) s[++top]=f[i];
	while(top) pd(s[top--]);
	while(!isroot(x)) {
		if(!isroot(f[x])) {
			if(is(x)^is(f[x])) spin(x);
			else spin(f[x]);
		}
		spin(x);
	}
}
int find(int x) {
	if(sum[son[x][0]]) return find(son[x][0]);
	if(a[x]) return x;
	return find(son[x][1]);
}
void acc(int x) {int y=0;while(x) splay(x),son[x][1]=y,up(x),y=x,x=f[x];}
void evert(int x) {acc(x),splay(x),rev[x]^=1;}
void link(int x,int y) {evert(x),f[x]=y;}
int main()
{
	int bj,x,y;
	n=read(),q=read();
	for(int i=1;i<n;++i) x=read(),y=read(),link(x,y);
	while(q--) {
		bj=read(),x=read();
		if(!bj) splay(x),a[x]^=1,up(x);
		else {
			evert(1),acc(x),splay(x);
			if(!sum[x]) puts("-1");
			else printf("%d\n",find(x));
		}
	}
	return 0;
}

QTREE4

要么改变一个点的颜色(黑/白),要么询问两个白点的最大边权和

动态点分治

首先建立点分树。所谓点分树,即跑一遍点分的过程,每次找到一个重心,然后将它若干个子树里的重心向它连边,这样建出来的一棵树。树高显然是log的。

所以对于修改操作,我们就可以从它在点分树上的位置暴力跳父亲修改。

维护三个堆:

  1. 每个重心上一个堆C,存其点分树上子树里的所有点到其点分树上父亲,在原树上的距离。
  2. 每个重心上一个堆B,存其点分树上子树的C堆堆顶。
  3. 一个全局堆A存答案,即每个重心的B堆中次大值+最大值。

可删除堆就搞两个优先队列瞎搞即可,可以看代码。

然后修改操作并不是很困难,假设现在在修改对点分树上祖先x的影响,x的父亲是p。首先修改x的C堆,再修改p的B堆,最后修改A堆即可。

#include<bits/stdc++.h>
using namespace std;
int read() {
    int q=0,w=1;char ch=' ';
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
    return q*w;
}
#define RI register int
const int N=100005,inf=0x3f3f3f3f;
int n,Q,tot,mx,SZ,rt,wjs;
int h[N],ne[N<<1],to[N<<1],w[N<<1],sz[N],vis[N],fa[N],col[N];
int dep[N],dis[N],f[N][17];

void add(RI x,RI y,RI z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,w[tot]=z;}
void dfs(RI x,RI las) {
    f[x][0]=las,dep[x]=dep[las]+1;
    for(RI i=1;i<=16;++i) f[x][i]=f[f[x][i-1]][i-1];
    for(RI i=h[x];i;i=ne[i])
        if(to[i]!=las) dis[to[i]]=dis[x]+w[i],dfs(to[i],x);
}
int lca(RI x,RI y) {
    if(dep[x]<dep[y]) swap(x,y);
    for(RI i=16;i>=0;--i) if(dep[f[x][i]]>=dep[y]) x=f[x][i];
    if(x==y) return x;
    for(RI i=16;i>=0;--i)
        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
int dist(RI x,RI y) {return dis[x]+dis[y]-dis[lca(x,y)]*2;}

struct heap{
    int sz;priority_queue<int>q1,q2;
    void push(RI x) {++sz,q1.push(x);}
    void pop(RI x) {--sz,q2.push(x);}
    int top() {
        while(q2.size()&&q1.top()==q2.top()) q1.pop(),q2.pop();
        if(!q1.size()) return -inf;
        else return q1.top();
    }
    int getans() {
        if(sz<2) return 0;
        RI x=top();pop(x);
        RI y=top();push(x);
        return max(x+y,0);
    }
    void orz(RI x,RI bj) {if(bj) pop(x); else push(x);}
}A,B[N],C[N];
void getrt(RI x,RI las) {
    sz[x]=1; RI bl=0;
    for(RI i=h[x];i;i=ne[i])
        if(to[i]!=las&&!vis[to[i]])
            getrt(to[i],x),sz[x]+=sz[to[i]],bl=max(bl,sz[to[i]]);
    bl=max(bl,SZ-sz[x]);
    if(bl<mx) mx=bl,rt=x;
}
void workdis(RI st,RI p,RI x,RI las) {
    C[st].push(dist(x,p));
    for(RI i=h[x];i;i=ne[i])
        if(to[i]!=las&&!vis[to[i]]) workdis(st,p,to[i],x);
}yig
void prework(RI x) {
    vis[x]=1,workdis(x,fa[x],x,0);
    for(RI i=h[x];i;i=ne[i])
        if(!vis[to[i]]) {
            mx=inf,SZ=sz[to[i]],getrt(to[i],x);RI krt=rt;
            fa[krt]=x,prework(krt),B[x].push(C[krt].top());
        }
    B[x].push(0),A.push(B[x].getans());
}
void chan(RI x,RI bj) {//1:变黑 0:变白
    RI l1=B[x].getans(),l2;
    B[x].orz(0,bj),l2=B[x].getans();
    if(l1!=l2) A.pop(l1),A.push(l2);
    for(RI now=x,p=fa[x];p;now=yigp,p=fa[now]) {
        RI t1=C[now].top(),t2;
        C[now].orz(dist(x,p),bj),t2=C[now].top();
        if(t1==t2) continue;
        l1=B[p].getans();
        if(t1!=-inf) B[p].pop(t1);
        if(t2!=-inf) B[p].push(t2);
        l2=B[p].getans();
        if(l1!=l2) A.pop(l1),A.push(l2);
    }
}
int main()
{
    RI x,y,z;char ch[10];
    n=read();
    for(RI i=1;i<n;++i)
        x=read(),y=read(),z=read(),add(x,y,z),add(y,x,z);
    dfs(1,0),mx=inf,SZ=n,getrt(1,n),prework(rt);
    wjs=n,Q=read();
    while(Q--) {
        scanf("%s",ch);
        if(ch[0]=='A') {
            if(!wjs) puts("They have disappeared.");
            else printf("%d\n",A.top());
        }yig
        else x=read(),wjs+=(col[x]?1:-1),col[x]^=1,chan(x,col[x]);
    }
    return 0;
}

QTREE5

点有黑白两色,修改点的颜色,询问离一个点最近的白点离它的距离。

用子树信息LCT,用类似QTREE6的做法(因为我先做的6嘛),每个节点用一个可删除堆维护这个节点的虚子树中离它最近的白点到它的距离。再维护实链(一棵splay)上的信息:一个是深度最浅的节点(splay上最左边的节点)到该条链上离它最近的白点的距离,一个是深度最深的节点(最右边的)到离它最近的白点的距离。

即可完成询问和修改操作。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int N=100005,inf=0x3f3f3f3f;
int n,m,tot,tim;
int h[N],ne[N<<1],to[N<<1],col[N];
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}

struct heap{
	priority_queue<int,vector<int>,greater<int> > A,B;
	void push(int x) {A.push(x);}
	void del(int x) {B.push(x);}
	int top() {
		while(!A.empty()&&!B.empty()&&A.top()==B.top()) A.pop(),B.pop();
		return A.empty()?inf:A.top();
	}
};
struct LCT{
	int s[N][2],f[N],sz[N],lmi[N],rmi[N];heap kmi[N];
	int is(int x) {return s[f[x]][1]==x;}
	int isroot(int x) {return s[f[x]][0]!=x&&s[f[x]][1]!=x;}
	void up(int x) {
		int ls=s[x][0],rs=s[x][1];
		sz[x]=sz[ls]+sz[rs]+1;
		lmi[x]=min(lmi[ls],sz[ls]+min(col[x]?inf:0,min(kmi[x].top(),lmi[rs]+1)));
		rmi[x]=min(rmi[rs],sz[rs]+min(col[x]?inf:0,min(kmi[x].top(),rmi[ls]+1)));
	}
	void spin(int x) {
		int fa=f[x],g=f[fa],t=is(x);
		if(!isroot(fa)) s[g][is(fa)]=x;
		f[fa]=x,f[x]=g,f[s[x][t^1]]=fa;
		s[fa][t]=s[x][t^1],s[x][t^1]=fa;
		up(fa),up(x);
	}
	void splay(int x) {
		while(!isroot(x)) {
			if(!isroot(f[x])) {
				if(is(x)^is(f[x])) spin(x);
				else spin(f[x]);
			}
			spin(x);
		}
	}
	void acc(int x) {
		int y=0;
		while(x) {
			splay(x);
			if(lmi[y]<inf) kmi[x].del(lmi[y]+1);
			if(lmi[s[x][1]]<inf) kmi[x].push(lmi[s[x][1]]+1);
			s[x][1]=y,up(x),y=x,x=f[x];
		}
	}
	void link(int y,int x) {acc(y),splay(y),splay(x),f[x]=y;}
	void chancol(int x) {acc(x),splay(x),col[x]^=1,up(x);}
	int query(int x)
		{acc(x),splay(x);return min(rmi[x],min(col[x]?inf:0,kmi[x].top()));}
}T;

void dfs(int x,int las) {
	if(las) T.link(las,x);
	for(RI i=h[x];i;i=ne[i]) if(to[i]!=las) dfs(to[i],x);
}
int main()
{
	int x,y,op;
	n=read();
	for(RI i=1;i<=n;++i) col[i]=1,T.sz[i]=1,T.lmi[i]=T.rmi[i]=inf;
	T.lmi[0]=T.rmi[0]=inf;
	for(RI i=1;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
	dfs(1,0);
	m=read();
	while(m--) {
		op=read(),x=read();
		if(op==0) T.chancol(x);
		else {
			int ans=T.query(x);
			if(ans<inf) printf("%d\n",ans);
			else puts("-1");
		}
	}
	return 0;
}

QTREE6

点分为黑点和白点,操作是将一个点变色,询问是询问某个点所处的同色连通块大小。

第一种做法:子树信息LCT

开一棵白点LCT和一棵黑点LCT,每次改了点的颜色后,肯定不能暴力与它相连的点做link或cut,就将每个点和与它父亲相连的边看做一体。是白点,在白LCT中这条边就存在,黑LCT中就不存在,反之亦然。

这样一来,如果这个点是白点,在白LCT中的一个连通块,只有可能深度最浅的那个节点不是白点。黑点亦然。

用子树信息LCT记录子树中的点数。

询问点 x x x时,access(x),获得根 y y y,然后splay(y)。如果 y y y x x x同色,答案是 s z ( y ) sz(y) sz(y),否则答案是splay上 y y y的右儿子的sz。

修改时,只需做link和cut操作。注意本题要维护有根树,要善用access来避免父亲信息出现错误,具体可以看代码中的link和cut。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int N=100005;
int n,m,tot;
int h[N],ne[N<<1],to[N<<1],col[N],fa[N];

struct LCT{
	int s[N][2],f[N],sz[N],ksz[N];
	int is(int x) {return s[f[x]][1]==x;}
	int isroot(int x) {return s[f[x]][0]!=x&&s[f[x]][1]!=x;}
	int up(int x) {sz[x]=ksz[x]+1+sz[s[x][0]]+sz[s[x][1]];}
	void spin(int x) {
		int ff=f[x],g=f[ff],t=is(x);
		if(!isroot(ff)) s[g][is(ff)]=x;
		f[ff]=x,f[x]=g,f[s[x][t^1]]=ff;
		s[ff][t]=s[x][t^1],s[x][t^1]=ff;
		up(ff),up(x);
	}
	void splay(int x) {
		while(!isroot(x)) {
			if(!isroot(f[x])) {
				if(is(x)^is(f[x])) spin(x);
				else spin(f[x]);
			}
			spin(x);
		}
	}
	void acc(int x) {
		int y=0;
		while(x) splay(x),ksz[x]+=sz[s[x][1]]-sz[y],s[x][1]=y,up(x),y=x,x=f[x];
	}
	void link(int y,int x)
		{acc(y),splay(y),splay(x),f[x]=y,ksz[y]+=sz[x],up(y);}
	void cut(int y,int x)
		{acc(x),splay(x),s[x][0]=f[s[x][0]]=0,up(x);}
	int getrt(int x) {while(s[x][0]) x=s[x][0]; return x;}
	int query(int x,int c) {
		acc(x),splay(x);int y=getrt(x);splay(y);
		if(col[y]==c) return sz[y];
		else return sz[s[y][1]];
	}
}T[2];

void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void dfs(int x,int las) {
	if(las) fa[x]=las,T[1].link(las,x);
	for(RI i=h[x];i;i=ne[i])
		if(to[i]!=las) dfs(to[i],x);
}
int main()
{
	int x,y,op;
	n=read();
	for(RI i=1;i<=n;++i) T[1].sz[i]=T[0].sz[i]=1,col[i]=1;
	for(RI i=1;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
	dfs(1,0),m=read();
	while(m--) {
		op=read(),x=read();
		if(!op) printf("%d\n",T[col[x]].query(x,col[x]));
		else {
			if(fa[x]) T[col[x]].cut(fa[x],x);
			col[x]^=1;
			if(fa[x]) T[col[x]].link(fa[x],x);
		}
	}
	return 0;
}

第二种做法:树剖+树状数组。

(PS.这种做法是一年前我刚学树剖的时候,被Cai坑着去写的,适合于想要练习树剖 或想找虐 的人士食用)

我们在每个点处记下该点为黑/白时,以它为根的子树中,它所处的连通块大小(记为f(x,0)和f(x,1))。这个东西按照dfs序存储,同时也记录按照dfs序的黑点个数前缀和,都用树状数组维护,这样,我们可以利用二分查找迅速找到一个点所处的同色连通块中,深度最小的那个点,可以获得答案。

而修改x的颜色的时候,有点儿麻烦,需要仔细思考,x的父亲与x连通的深度最小点的父亲都要做修改,扯不明白就看代码里的修改函数吧……

树状数组中,为了便于区间修改,使用了差分,所以f(x,0)和f(x,1)的真实值是树状数组中前缀和的值。

#include<bits/stdc++.h>
using namespace std;
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int N=100005;
int n,Q,tot,now;
int h[N],ne[N<<1],to[N<<1],sz[N],top[N],pos[N],a[N],fa[N];
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void dfs1(int x,int las) {
	sz[x]=1,fa[x]=las;
	for(int i=h[x];i;i=ne[i])
		if(to[i]!=las) dfs1(to[i],x),sz[x]+=sz[to[i]];
}
void dfs2(int x,int las) {
	int bj=0,mx=0; pos[x]=++now;
	for(int i=h[x];i;i=ne[i])
		if(to[i]!=las&&sz[to[i]]>mx) mx=sz[to[i]],bj=to[i];
	if(!bj) return;
	top[bj]=top[x],dfs2(bj,x);
	for(int i=h[x];i;i=ne[i])
		if(to[i]!=las&&to[i]!=bj) top[to[i]]=to[i],dfs2(to[i],x);
}
#define lowbit(x) (x&(-x))
int tr[3][N];
//0:若该点为黑点,以其为根连通块大小,1:若该点为白点。为了便于区间修改,使用了差分,真实值是前缀和值
//2:记录dfs序小于等于该编号的黑点数量,便于二分查找
void add(int o,int x,int num) {while(x<=n) tr[o][x]+=num,x+=lowbit(x);}
int query(int o,int x) {
	int re=0;
	while(x) re+=tr[o][x],x-=lowbit(x);
	return re;
}
int erf(int l,int r,int cx) {//二分查找
	int re,mid;
	while(l<=r) {
		mid=(l+r)>>1;
		int js=query(2,r)-query(2,mid-1);
		if((cx==0&&js==r-mid+1)||(cx&&js==0)) re=mid,r=mid-1;
		else l=mid+1;
	}
	return re;
}
int find(int x) {
	int cx=a[x];
	while(x) {
		int js=query(2,pos[x])-query(2,pos[top[x]]-1);//该路径上黑点数量
		if((cx==0&&js==pos[x]-pos[top[x]]+1)||(cx&&js==0))
			if(a[fa[top[x]]]!=cx) return pos[top[x]];//查看top[x]的父亲是否同色
			else {x=fa[top[x]];continue;}
		return erf(pos[top[x]],pos[x],cx);
	}
	return pos[1];//如果到最后都没有找到不同色的点,说明根节点1与x同色
}
void work(int mb,int x,int cx,int num) {//mb:目标节点编号
	if(mb==pos[x]) {//特殊处理
		if(fa[x]) add(cx,pos[fa[x]],num),add(cx,pos[fa[x]]+1,-num);
		return;
	}
	x=fa[x];
	while(x) {
		if(pos[top[x]]<=mb&&mb<=pos[x]) {
			if(mb!=pos[top[x]]) add(cx,mb-1,num),add(cx,pos[x]+1,-num);
			else {
				int kl=pos[fa[top[x]]];
				add(cx,pos[top[x]],num),add(cx,pos[x]+1,-num);
				if(kl) add(cx,kl,num),add(cx,kl+1,-num);
			}
			break;
		}
		add(cx,pos[top[x]],num),add(cx,pos[x]+1,-num);
		x=fa[top[x]];
	}
}
int main()
{
    int x,y,bj;
    n=read();
    for(int i=1;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
    dfs1(1,0),top[1]=1,dfs2(1,0);
    for(int i=1;i<=n;++i)
    	add(0,pos[i],sz[i]),add(0,pos[i]+1,-sz[i]),add(2,pos[i],1);
    add(1,1,1),Q=read();
    while(Q--) {
    	bj=read(),x=read();
    	if(!bj) printf("%d\n",query(a[x],find(x)));
    	else {
    		work(find(x),x,a[x],-query(a[x],pos[x]));//修改x同色连通块一些点的f(x,a[x])值
    		a[x]^=1,add(2,pos[x],a[x]==0?1:-1);//对2作修改
    		work(find(x),x,a[x],query(a[x],pos[x]));
    	}
    }
    return 0;
}

QTREE7

点有黑白两种颜色的点权,修改操作为修改颜色或修改点权,询问一个点所在的同色连通块中的最大点权。

也像QTREE6一样,维护一白一黑两个子树信息LCT,唯一的问题就是,求max这个操作是不资磁撤销的。所以维护虚儿子信息的部分,用一个set或者可删除堆,存所有虚儿子提供的信息即可。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0,w=1;char ch=' ';
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') w=-1,ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q*w;
}
const int N=100005,inf=0x3f3f3f3f;
int n,m,tot;
int h[N],ne[N<<1],to[N<<1],col[N],fa[N];

struct Heap{
	priority_queue<int> A,B;
	void push(int x) {A.push(x);}
	void del(int x) {B.push(x);}
	int top() {
		while(!A.empty()&&!B.empty()&&A.top()==B.top()) A.pop(),B.pop();
		return A.empty()?-inf:A.top();
	}
};
struct LCT{
	int s[N][2],f[N],mx[N],v[N];Heap kmx[N];
	int is(int x) {return s[f[x]][1]==x;}
	int isroot(int x) {return s[f[x]][0]!=x&&s[f[x]][1]!=x;}
	void up(int x)
		{mx[x]=max(max(v[x],kmx[x].top()),max(mx[s[x][0]],mx[s[x][1]]));}
	void spin(int x) {
		int ff=f[x],g=f[ff],t=is(x);
		if(!isroot(ff)) s[g][is(ff)]=x;
		f[ff]=x,f[x]=g,f[s[x][t^1]]=ff;
		s[ff][t]=s[x][t^1],s[x][t^1]=ff;
		up(ff),up(x);
	}
	void splay(int x) {
		while(!isroot(x)) {
			if(!isroot(f[x])) {
				if(is(x)^is(f[x])) spin(x);
				else spin(f[x]);
			}
			spin(x);
		}
	}
	void acc(int x) {
		int y=0;
		while(x) {
			splay(x),kmx[x].del(mx[y]),kmx[x].push(mx[s[x][1]]);
			s[x][1]=y,up(x),y=x,x=f[x];
		}
	}
	void link(int y,int x)
		{acc(y),splay(y),splay(x),f[x]=y,kmx[y].push(mx[x]),up(y);}
	void cut(int y,int x)
		{acc(x),splay(x),s[x][0]=f[s[x][0]]=0,up(x);}
	int getrt(int x) {while(s[x][0]) x=s[x][0];return x;}
	int query(int x,int c) {
		acc(x),splay(x);int y=getrt(x);splay(y);
		return col[y]==c?mx[y]:mx[s[y][1]];
	}
	void chan(int x,int val) {acc(x),splay(x),v[x]=val,up(x);}
}T[2];

void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void dfs(int x,int las) {
	if(las) fa[x]=las,T[col[x]].link(las,x);
	for(RI i=h[x];i;i=ne[i]) if(to[i]!=las) dfs(to[i],x);
}
int main()
{
	int x,y,op;
	n=read();
	for(RI i=1;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
	for(RI i=1;i<=n;++i) col[i]=read();
	for(RI i=1;i<=n;++i) T[0].v[i]=T[1].v[i]=T[0].mx[i]=T[1].mx[i]=read();
	T[0].v[0]=T[1].v[0]=T[0].mx[0]=T[1].mx[0]=-inf;
	dfs(1,0),m=read();
	while(m--) {
		op=read(),x=read();
		if(op==0) printf("%d\n",T[col[x]].query(x,col[x]));
		else if(op==1) {
			if(fa[x]) T[col[x]].cut(fa[x],x);
			col[x]^=1;
			if(fa[x]) T[col[x]].link(fa[x],x);
		}
		else y=read(),T[0].chan(x,y),T[1].chan(x,y);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值