最近公共祖先——解题报告

题目链接:

http://132.232.5.128:666/submission/6690

题目大意:

给定一棵 n n n个结点的有根树,结点编号为 1 ∼ n 1 \sim n 1n,其中根节点为 1 1 1号结点。每个结点都对应着一种颜色(黑/白)和一个固定的权值,初始时所有结点的颜色都为白色。现在你需要实现以下两种操作:

  • M o d i f y   v Modify\ v Modify v:将结点 v v v的颜色修改为黑色;
  • Q u e r y   v Query\ v Query v:找到一个黑色结点 u u u,使得结点 u u u v v v的最近公共祖先 z z z对应的权值尽可能大,输出 z z z的权值。如果此时树中不存在黑色结点,输出 − 1 −1 1

题目分析:

1.考虑一个点被涂成了黑色后的影响:

  • 黑点的子树上的点的答案将会受到影响,答案取原来的值和当前黑点权值的 m a x max max
  • 还会影响到其他所有点,答案取原来的值和这个点和黑点的 L C A LCA LCA的权值的 m a x max max

2.我们发现,更新其他点时,我们可以顺着黑点往上遍历他的父节点,把这些点全当成黑色点来对其子节点更新(和黑色点影响子树的修改方式相同)。
3.如果之前这个点已经被修改成了黑色,代表这个点及其祖先的权值都被使用过了,那么就不需要更新答案了。
4.当我们这样做后,发现时间复杂度为 O ( n 2 ) O(n^2) O(n2),所以我们可以根据 D F S DFS DFS序维护一颗线段树,将时间复杂度下降至 O ( n l o g n ) O(nlogn) O(nlogn)。因为一颗子树在 D F S DFS DFS序下的编号是一段连续的区间。直接进行区间修改即可。
5.最后注意一个坑:你可以同时修改整个一段区间的值,但是绝对不能用线段树的两个子节点去更新父节点的值,整个一段区间可以分解为两个区间,但两个区间不能合并。部分答案无法代表整体的答案

正解程序:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long ll;
const ll maxn=100010,maxm=200010;
struct node
{
	ll v;
	ll next;
}e[maxm];
ll p[maxn],t=0;
void insert(ll u,ll v)
{
	e[t].v=v;
	e[t].next=p[u];
	p[u]=t++;
}
ll n,m,val[maxn];
ll count1,fa[maxn],dfn[maxn],son[maxn];
void dfs(ll u)
{
	dfn[u]=++count1;
	son[u]=1;
	for(ll i=p[u];i!=-1;i=e[i].next)
	{
		ll v=e[i].v;
		if(v!=fa[u])
		{
			fa[v]=u;
			dfs(v);
			son[u]+=son[v];
		}	
	}	
}
ll tree[maxn<<2];
void change(ll pos,ll l,ll r,ll s,ll e,ll num)
{
	if(num<tree[pos] || s>e)
		return;
	if(s<=l && r<=e)
	{
		tree[pos]=max(tree[pos],num);
		return;
	}
	ll mid=(l+r)>>1;
	if(s<=mid)
		change(pos<<1,l,mid,s,e,num);
	if(e>mid)
		change(pos<<1|1,mid+1,r,s,e,num);
}
ll getans(ll pos,ll l,ll r,ll x)
{
	if(l==r)
		return tree[pos];
	ll mid=(l+r)>>1;
	if(x<=mid)
		return max(tree[pos],getans(pos<<1,l,mid,x));
	else
		return max(tree[pos],getans(pos<<1|1,mid+1,r,x));
}
void work(ll start,ll end1,ll num)
{
	if(end1==0)//处理子树
		change(1,1,n,dfn[start],dfn[start]+son[start]-1,num);
	else
	{
		change(1,1,n,dfn[start],dfn[end1]-1,num);
		change(1,1,n,dfn[end1]+son[end1],dfn[start]+son[start]-1,num);
	}
}
bool black[maxn];
int main()
{
	memset(p,-1,sizeof(p));
	scanf("%lld%lld",&n,&m);
	for(ll i=1;i<=n;i++)
		scanf("%lld",&val[i]);
	for(ll i=1;i<=n-1;i++)
	{
		ll u,v;
		scanf("%lld%lld",&u,&v);
		insert(u,v);
		insert(v,u);
	}
	count1=0;
	dfs(1);
	memset(black,false,sizeof(black));
	memset(tree,-1,sizeof(tree));
	for(ll i=1;i<=m;++i)
	{
		char str[10];
		ll point;
		scanf("%s",str);
		scanf("%lld",&point);
		if(str[0]=='M')
		{
			for(ll last=0;point;last=point,point=fa[point])
			{
				work(point,last,val[point]);
				if(black[point])
					break;
				black[point]=true;
			}
		}
		else
			printf("%lld\n",getans(1,1,n,dfn[point]));
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值