ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

CF276E Little Girl and Problem on Trees

2020-08-17 19:31:33  阅读:287  来源: 互联网

标签:Little 根上 树状 dep include 权值 Problem CF276E 节点


此题为本人在Codeforces上独立AC的第一道洛谷紫色难度的题目,极具纪念意义,特此发布题解。

题意简述

题目链接

  给定一棵无边权的树,初始点权均为0,保证除根节点外其余节点的度数不超过2。执行m次操作,每次操作为以下两种操作之一:(1)0 u x d 将距离节点u不超过d的点的权值加上x;(2)1 u 查询节点u的权值。

算法概述

  显然,树的形态就是根上挂了很多条链,考虑利用这个形态的特殊性。

  首先,如果距离d以内的点均与u在同一条链上,那么很好处理,直接线段树维护链上的区间权值即可,因为同一条链上的点dfs序是连续的,以下用id[u]表示节点u的dfs序。

  但是这道题并没有那么简单,这个d完全有可能超过dep[u],那么就会绕过根影响到u所在的链以外的点,如果直接将链以外的点的权值也一同处理了,则时间复杂度显然不行。

  考虑在根上挂标记,将需要增加的点权暂时保留在根上。以val[i]表示距离根i以内的点需要加上的权值,这个定义等价于深度小于等于i的点需要加上的权值(定义根节点深度为0)。

  我们用dep[u]表示节点u的深度,那么在查询节点u时,其保留在根上的还需增加的点权即为val[dep[u],dep[u]+1,…,n-1](一共n个节点,深度最大为n-1),也就是一段后缀和,故我们可以用树状数组维护。

  于是,我们就得到了一个可以很好解决这个问题的算法:

  对于修改操作:若d<dep[u],直接线段树维护即可;若d>=dep[u],则先在根上加标记,val[d-dep[u]]加上x,然后处理链,由于在根上加了标记之后,原链上距离根d-dep[u]以内的点已经被覆盖了,所以考虑有没有过多覆盖(即覆盖的范围超出了本来需要修改的范围),这显然不可能(反证:若过多覆盖,即d-dep[u]>dep[u]+d => dep[u]<0,矛盾!)。所以已覆盖的范围是必然小于等于我们原本需要覆盖的范围的,那么我们只需考虑还需修改的范围(但是已覆盖的范围可能已经包括了链的末端,所以还需要特判)。这个范围的顶端显然是链顶(id[u]-dep[u]+1)加上已覆盖的范围(d-dep[u]),即id[u]-2*dep[u]+d+1,而末端可能为原链的链尾(id[u]+len[ch[u]]-dep[u]),也有可能是id[u]+d,两者相比较一下取小即可。然后对于这个范围,用线段树做一下区间修改即可。

  为了快速得到链尾,我们可以预处理出ch[u]表示节点u所在的链的编号,len[i]表示编号为i的链的长度,即链尾节点的深度。

  对于查询操作:一部分是已修改的权值,直接线段树单点查询即可,另一部分是待修改的权值,被我们挂在根上,故树状数组区间查询后缀和即可。

  还有一个细节需要注意,由于树状数组维护的下标范围是从1开始的,而我们的深度最小是0,所以我们需要在对树状数组执行操作时的下标整体后移一位。

参考代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+10;
struct Edge{
	int to,next;
}edge[N<<1];int idx;
int h[N];

void add_edge(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;}

struct Segment_Tree{
	int l,r;
	ll sum;
	ll add;
	#define l(p) tree[p].l
	#define r(p) tree[p].r
	#define sum(p) tree[p].sum
	#define add(p) tree[p].add
}tree[N<<2];

void push_up(int p){sum(p)=sum(p<<1)+sum(p<<1|1);}

void spread(int p)
{
	if(add(p))
	{
		sum(p<<1)+=add(p)*(r(p<<1)-l(p<<1)+1);
		sum(p<<1|1)+=add(p)*(r(p<<1|1)-l(p<<1|1)+1);
		add(p<<1)+=add(p);
		add(p<<1|1)+=add(p);
		add(p)=0; 
	}
}

void build(int p,int l,int r)
{
	l(p)=l,r(p)=r;
	if(l==r)return;
	int mid=l+r>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
}

void change(int p,int l,int r,int v)
{
	if(l<=l(p)&&r>=r(p))
	{
		sum(p)+=(ll)v*(r(p)-l(p)+1);
		add(p)+=v;
		return;
	}
	spread(p);
	int mid=l(p)+r(p)>>1;
	if(l<=mid)change(p<<1,l,r,v);
	if(r>mid)change(p<<1|1,l,r,v);
	push_up(p);
}

int query(int p,int pos)
{
	if(l(p)==r(p))return sum(p);
	spread(p);
	int mid=l(p)+r(p)>>1;
	if(pos<=mid)return query(p<<1,pos);
	else return query(p<<1|1,pos);
}

int dep[N],id[N];
int len[N],ch[N];
int timestamp,cnt;

void dfs(int p,int f,int ch_id)
{
	id[p]=++timestamp;
	dep[p]=dep[f]+1;
	ch[p]=ch_id; //处理链的编号 
	len[ch_id]=max(len[ch_id],dep[p]); //每条链的最大深度 
	for(int i=h[p];~i;i=edge[i].next)
	{
		int to=edge[i].to;
		if(to==f)continue;
		dfs(to,p,ch_id);
	}
}

void init()
{
	dep[1]=0;
	id[1]=++timestamp;
	for(int i=h[1];~i;i=edge[i].next)
	{
		int to=edge[i].to;
		dfs(to,1,++cnt);
	}
}

int tr[N];
int n,m;

int lowbit(int x){return x&-x;}

void add_tree(int x,int y){for(;x<=n;x+=lowbit(x))tr[x]+=y;}

int ask(int x)
{
	int ans=0;
	for(;x;x-=lowbit(x))ans+=tr[x];
	return ans;
}

int main()
{
	scanf("%d%d",&n,&m);
	memset(h,-1,sizeof h);
	for(int i=1;i<=n-1;i++)
	{
		int u,v;scanf("%d%d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	init();
	build(1,1,n);
	while(m--)
	{
		int tp;scanf("%d",&tp);
		if(!tp) //修改操作 
		{
			int u,x,d;scanf("%d%d%d",&u,&x,&d);
			if(u==1)add_tree(d+1,x); //根节点直接特殊处理,d+1是下标后移一位 
			else if(d<dep[u]) //完全在链上的情况 
				change(1,max(id[u]-d,id[u]-dep[u]+1),min(id[u]+len[ch[u]]-dep[u],id[u]+d),x); //顶端取max,末端取min 
			else //需要在根上挂标记的情况 
			{
				add_tree(d-dep[u]+1,x); //处理根上的标记,+1同样也是因为下标后移一位 
				if(id[u]+len[ch[u]]-dep[u]<id[u]+d-2*dep[u]+1)continue; //链尾<未被覆盖的第一个点,即链尾已被覆盖 
				change(1,id[u]+d-2*dep[u]+1,min(id[u]+len[ch[u]]-dep[u],id[u]+d),x); //末端取min 
			}
		}
		else //查询操作 
		{
			int u;scanf("%d",&u);
			int ans=query(1,id[u]); //已经被修改的部分 
			int sum=ask(n)-ask(dep[u]); //还保留在根上的待修改部分 
			printf("%d\n",ans+sum); //两部分相加即为答案 
		}
	}
	
	return 0;
}

  

标签:Little,根上,树状,dep,include,权值,Problem,CF276E,节点
来源: https://www.cnblogs.com/ninedream/p/13519528.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有