codeforces 916E Jamie and Tree 题解

本文探讨了一种处理带权树动态子树操作的方法,包括更改根节点、对子树进行加值操作以及查询子树点权和。通过定义特定的函数如LCA(u,v,r)和SUB(u,r),并利用DFS序和线段树维护子树信息,实现了高效的算法解决方案。

题意简述

给定一个 n≤105n\le 10^5n105 个点带权的树,初始根为 111。支持三种操作:
1 r 把根换成 rrr
2 u v xLCA(u,v)LCA(u,v)LCA(u,v) 的子树整体加 xxx
3 u 查询 uuu 的子树点权和

操作数≤105\le 10^5105。每个点权 ≤108\le 10^8108

思路

如果没有 111 操作,那 2,32,32,3 操作显然就是一个板子。

但是现在有了 111 操作。 那么我们就要考虑几个问题:

  1. 如何求 LCA
  2. 如何知道子树是哪一块 (对其进行求和或加值)

当然,无根树肯定是不好讨论的。我们先假定 111 为根。 然后判断以 rrr 为根的子树长什么样。

为了不混淆,设:
LCA(u,v,r)LCA(u,v,r)LCA(u,v,r) 表示以 rrr 为根,u,vu,vu,vLCALCALCA
SUB(u,r)SUB(u,r)SUB(u,r) 表示以 rrr 为根,uuu 的子树。

路径不需要重新定义,因为路径是唯一确定的,和根无关。

求LCA

假设我们要求 LCA(u,v,r)LCA(u,v,r)LCA(u,v,r)

  1. 如果当前的根 rrru,vu,vu,v 的路径上,那么显然 LCA(u,v,r)=rLCA(u,v,r)=rLCA(u,v,r)=r
  2. 否则,设 L=LCA(u,v,1)L=LCA(u,v,1)L=LCA(u,v,1)。 如果当前的根在 SUB(L,1)SUB(L,1)SUB(L,1) 外,那么 LCA(u,v,r)=LLCA(u,v,r)=LLCA(u,v,r)=L,就不会有影响
  3. 再否则,LCA(u,v,r)LCA(u,v,r)LCA(u,v,r) 就是 u,vu,vu,v 路径上的某个点了。稍微想一下,这个点是 LCA(u,r,1)LCA(u,r,1)LCA(u,r,1) 或者 LCA(v,r,1)LCA(v,r,1)LCA(v,r,1) 中的一个。 再仔细想想,似乎是选深的那一个(以 111 为根)。那么如果同样深怎么办呢?
    继续想想,LCA(u,r,1)LCA(u,r,1)LCA(u,r,1)LCA(v,r,1)LCA(v,r,1)LCA(v,r,1) 肯定一个是 LCA(u,v,1)LCA(u,v,1)LCA(u,v,1) ,一个是 LCA(u,v,r)LCA(u,v,r)LCA(u,v,r)。如果深度相同,那就说明 LCA(u,r,1)=LCA(v,r,1)=LCA(u,v,1)LCA(u,r,1)=LCA(v,r,1)=LCA(u,v,1)LCA(u,r,1)=LCA(v,r,1)=LCA(u,v,1) 。这个时候选哪个也无所谓了,反正都是对的。
求某个子树

假设我们要求 SUB(u,r)SUB(u,r)SUB(u,r)

  1. u=ru=ru=r。 最简单的情况,子树就是整棵树 (但是不能忘记特判!
  2. rrrSUB(u,1)SUB(u,1)SUB(u,1) 之外。 这也很显然,SUB(u,r)=SUB(u,1)SUB(u,r)=SUB(u,1)SUB(u,r)=SUB(u,1)
  3. 关键问题就是 rrrSUB(u,1)SUB(u,1)SUB(u,1) 之内的情况咋整。

我们画一张图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fjBtqhEx-1584208358232)(https://lightninguz2.github.io/blogimages/CF916E-2.jpg)]

很明显,此时 SUB(u,r)SUB(u,r)SUB(u,r) 就等于整棵树中减去 $SUB(k,1)。(这张图有一个没画全的地方,就是 uuu 上面可能还有别的点)

kkk 的值,也就是 rrruuu 的哪个子树里面,这个可以用倍增求。

总结

我们发现,最后的操作,只有子树加减,和单点求和的操作。甚至不用写树链剖分。这个用 DFSDFSDFS 序维护就能解决。

代码

{% fold %}

#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
	#define N 255555
	#define int long long 
	#define F(i,l,r) for(int i=l;i<=r;++i)
	#define D(i,r,l) for(int i=r;i>=l;--i)
	#define Fs(i,l,r,c) for(int i=l;i<=r;c)
	#define Ds(i,r,l,c) for(int i=r;i>=l;c)
	#define MEM(x,a) memset(x,a,sizeof(x))
	#define FK(x) MEM(x,0)
	#define Tra(i,u) for(int i=G.Start(u),v=G.To(i);~i;i=G.Next(i),v=G.To(i))
	#define p_b push_back
	#define sz(a) ((int)a.size())
	#define iter(a,p) (a.begin()+p)
	void R1(int &x)
	{
	    x=0;char c=getchar();int f=1;
	    while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
	    while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	    x=(f==1)?x:-x;
	}
	void Rd(int cnt,...)
	{
	    va_list args;
	    va_start(args,cnt);
	    F(i,1,cnt) 
	    {
	        int* x=va_arg(args,int*);R1(*x);
	    }
	    va_end(args);
	}
	class Graph
	{
	    public:
	        int head[N];
	        int EdgeCount;
	        struct Edge
	        {
	            int To,Label,Next;
	        }Ed[N<<1];
	        void clear(int _V=N,int _E=N<<1) 
	        {
	            memset(Ed,-1,sizeof(Edge)*(_E));
	            memset(head,-1,sizeof(int)*(_V));
	            EdgeCount=-1;
	        }
	        void AddEdge(int u,int v,int w=1)
	        {
	            Ed[++EdgeCount]=(Edge){v,w,head[u]};
	            head[u]=EdgeCount;
	        }
	        void Add2(int u,int v,int w=1) {AddEdge(u,v,w);AddEdge(v,u,w);}
	        int Start(int u) {return head[u];}
	        int To(int u){return Ed[u].To;}
	        int Label(int u){return Ed[u].Label;}
	        int Next(int u){return Ed[u].Next;}
	}G;
	class SegmentTree //线段树 区间加 区间求和
	{
	public:
		struct node{int l,r,s,a;}tree[N<<2];
		#define ls index<<1
		#define rs index<<1|1

		#define L tree[index].l
		#define R tree[index].r
		#define S tree[index].s
		#define A tree[index].a
		#define lL tree[ls].l
		#define lR tree[ls].r
		#define lS tree[ls].s
		#define lA tree[ls].a
		#define rL tree[rs].l
		#define rR tree[rs].r
		#define rS tree[rs].s
		#define rA tree[rs].a
		void Update(int index=1)
		{
			S=lS+rS;
		}
		void Build(int l,int r,int index=1)
		{
			L=l,R=r;S=A=0;
			if (l==r) {return;}
			int mid=(l+r)>>1;
			Build(l,mid,ls); Build(mid+1,r,rs);
			Update(index);
		}
		void AddOne(int x,int index=1)
		{
			A+=x;
			S+=(R-L+1)*x;
		}
		void PushDown(int index=1)
		{
			if (A) {AddOne(A,ls); AddOne(A,rs); A=0;}
		}
		void Add(int l,int r,int x,int index=1)
		{
			if (l>R or L>r) return;
			if (l<=L and R<=r) return AddOne(x,index);
			PushDown(index);
			Add(l,r,x,ls); Add(l,r,x,rs);
			Update(index);
		}
		int  Query(int l,int r,int index=1)
		{
			if (l>R or L>r) return 0;
			if (l<=L and R<=r) return S;
			PushDown(index);
			return Query(l,r,ls)+Query(l,r,rs);
		}
	}T;
	
	int n,m;
	int a[N];
	void Input()
	{
		Rd(2,&n,&m);
		F(i,1,n) R1(a[i]);
		G.clear();
		F(i,1,n-1) {int u,v;Rd(2,&u,&v); G.Add2(u,v);}
	}

	int In[N],Out[N],Time=0; 
	int fa[N][22],deep[N];
	void DFS(int u,int f)
	{
		In[u]=++Time;
		deep[u]=(u==f)?0:deep[f]+1;
		fa[u][0]=f; F(i,1,20) fa[u][i]=fa[fa[u][i-1]][i-1];
		Tra(i,u) if (v!=f)
		{
			DFS(v,u);
		}
		Out[u]=Time;
	}
	int FindSon(int u,int f) //求 u 在 f 的哪个子树里 (u相对于f的k值)
	{
		D(i,20,0) if (deep[fa[u][i]]>deep[f]) u=fa[u][i];
		return u;
	}
	bool IsFa(int u,int f) //这个判断f是否是u的祖先
	{
		if (deep[u]<deep[f]) return false;
		if (u==f) return true;
		return fa[FindSon(u,f)][0]==f;
	}
	//upd: 傻了,这个直接DFS序判断即可
	int LCA(int u,int v)
	{
		if (deep[u]<deep[v]) swap(u,v);
		D(i,20,0) if (deep[fa[u][i]]>=deep[v]) u=fa[u][i];
		if (u==v) return u;
		D(i,20,0) if (fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
		return fa[u][0];
	}
	//非常正常的LCA
	bool OnPath(int u,int v,int k)
	{
		int l=LCA(u,v);
		if (l==k) return true;
		return !IsFa(l,k) and (IsFa(u,k) or IsFa(v,k));
	}
	//判断k是否在u,v路径上
	void SubtAdd(int u,int x){T.Add(In[u],Out[u],x);}
	//子树加
	int  SubtSum(int u) {return T.Query(In[u],Out[u]);}
	//子树求和
	void Soviet()
	{
		DFS(1,1);
		T.Build(1,n);
		F(i,1,n) T.Add(In[i],In[i],a[i]); //初始化
		int root=1; //这个别忘了
		F(i,1,m)
		{
			int o;R1(o);
			if (o==1)
			{
				int u;R1(u);
				root=u; //非常简单的换根操作
			}
			if (o==2)
			{
				int u,v,x;Rd(3,&u,&v,&x);
				int l=LCA(u,v);
				if (OnPath(u,v,root)) 
				{
					//特判: 如果root在u,v路径上,那么LCA(u,v,root)==root,直接加整个树
					T.Add(1,n,x);
				}
				else if (In[root]<In[l] or Out[l]<In[root]) 
				{
					//如果root不在x子树内,那么没有影响,和根为1情况一样
					SubtAdd(l,x);
				}
				else
				{
					T.Add(1,n,x); //整个加
					if (deep[LCA(root,u)]<deep[LCA(root,v)]) swap(u,v);
					int k=FindSon(root,LCA(root,u));
					//找到k
					SubtAdd(k,-x);
					//把k的子树减掉
				}
			}
			if (o==3)
			{
				int u;R1(u);
				//这个和上面类似的
				if (root==u) 
				{
					printf("%lld\n",T.Query(1,n));
				}
				else if (In[root]<In[u] or Out[u]<In[root]) 
				{
					printf("%lld\n",SubtSum(u));
				}
				else 
				{
					int k=FindSon(root,u);
					printf("%lld\n",T.Query(1,n)-SubtSum(k));
					//整棵树-k的子树
				}
			}
		}
	}

	#define Flan void
	Flan IsMyWife()
	{
		Input();
		Soviet();
	}
	#undef int //long long 
}
int main()
{
	Flandre_Scarlet::IsMyWife();
	getchar();getchar();
	return 0;
}

{% endfold %}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值