一类用 LCT 维护信息的题目

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*/
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值