• 「CF383C Propagating tree」


    这应该属于一个比较麻烦的数据结构处理树上问题.

    题目大意

    给出一颗根节点编号为 (1) 的树,对于一个节点修改时在它的子树中对于深度奇偶性相同的节点加上这个权值,不同则减去这个值,单点查询.

    分析

    先给出这个问题的弱化版:

    给出一颗根节点编号为 (1) 的树,对于一个节点修改时在它的子树中节点也加上这个权值,单点查询.

    对于上面这个问题就好处理很多了,DFS序有一个性质,又称括号定理,在树中这个就会体现在再DFS序中以 (a) 为根节点的子树中的所有节点都在 (a) 的后面,且连续,于是对于一棵树的修改在DFS序中的就变成了区间修改,那么久可以拿出一些数据结构来维护这个东西.(例如线段树,树状数组,平衡树...)

    下面回到这个问题,可以发现对于深度不同的点的修改可能是不同的,只有两种修改((+val)(-val))而且这两个修改于这个点奇偶性有关,那就有一个鼻尖暴力的想法,既然和奇偶性有关,那么久维护两颗线段树,分别维护深度为奇数的点和深度为偶数的点的值,那就变会了第一个问题.

    细节处理

    1. 记得清空 (Lazy) 标记和下传 (Lazy) 标记.
    2. 在连边的时候需要连双向边,并且需要注意边的数组需要开两倍(这应该算是常识吧).
    3. 需要处理出每个点为根节点的树中深度为奇数的点的个数和深度为偶数的点的个数,需要用来计算修改的区间的开始和结束位置.
    4. 可以发现每次修改在两颗树都需要修改,对于加上 (val) 的部分很好计算,但是对于和它深度的奇偶性不同的节点的修改就比较麻烦了,所以在DFS的时候最好记录一下每个节点的第一个子节点是谁,即谁是在DFS序(不按奇偶分开)中的下一个节点,在修改的时候只要在另一颗线段树中以它为左端点,长度为这整颗子树中的所有深度的奇偶性与根节点不同的节点个数来修改.
    5. 记得需要特判一下这颗树只有一个节点的情况,这样会有一颗线段树中的大小为 (0),但是如果在建树的时候左端点从 (1) 开始,右端点为 (0),会无限递归,然后MLE.(亲身经历)

    代码实现

    #include<bits/stdc++.h>
    #define REP(i,first,last) for(int i=first;i<=last;++i)
    #define DOW(i,first,last) for(int i=first;i>=last;--i)
    using namespace std;
    const int maxN=3e5+7;
    int N,M;
    int val[maxN];
    //链式前向星,基础知识,不解释
    struct Edge
    {
    	int to,next;
    }edge[maxN*2];
    int edge_head[maxN];
    int edge_cnt=0;
    #define FOR(now) for(int i_edge=edge_head[now];i_edge;i_edge=edge[i_edge].next)
    #define TO edge[i_edge].to
    void AddEdge(int form,int to)//加边
    {
    	edge[++edge_cnt].to=to;
    	edge[edge_cnt].next=edge_head[form];
    	edge_head[form]=edge_cnt;
    }
    //下文中0表示偶数,1表示奇数,根节点为的深度为1
    int deep[maxN];//每个节点的深度
    int first_son[maxN];//记录每个节点的第一个子节点
    int place[maxN];//记录每格节点在按奇偶分开后的位置,因为之后存在奇偶中的一个,所以只要不需要分别
    int dfs[2][maxN];//记录奇偶的两个DFS序
    int dfs_cnt[2]={0,0};//记录DFS序长度
    int size[2][maxN];//记录每个节点为根节点的子树中深度为奇数的节点个数和深度为偶数的节点个数
    bool visit[maxN];//判断这个点是否被访问过
    #define TEAM (deep[now]&1)//方便使用
    void DFS(int now=1)
    {
    	visit[now]=1;//记录访问过这个节点
    	dfs[TEAM][++dfs_cnt[TEAM]]=now;//将这个点放入它改进入的DFS序中
    	place[now]=dfs_cnt[TEAM];//记录出现的位置
    	size[TEAM][now]=1;//自己和自己的深度的奇偶性自然相同
    	FOR(now)
    	{
    		if(!visit[TO])//如果没有访问过才访问
    		{
    			if(!first_son[now])//如果这个是他的第一个子节点
    			{
    				first_son[now]=TO;
    			}
    			deep[TO]=deep[now]+1;//子节点的深度为父节点的深度+1
    			DFS(TO);//继续DFS
    			//一下为记录以当前节点为根节点的子树中深度为奇和偶的节点各自的出现次数
    			size[TEAM^1][now]+=size[TEAM^1][TO];
    			size[TEAM][now]+=size[TEAM][TO];
    		}
    	}
    }
    #undef TEAM
    #undef FOR
    #undef TO
    struct LazyTag//Lazy标记
    {
    	int add;
    	void CleanLazyTag()//清空标记
    	{
    		add=0;
    	}
    }ForMake;
    LazyTag MakeLazyTag(int add)//制作一个标记
    {
    	ForMake.add=add;
    	return ForMake;
    }
    //线段树不是本文终于内容,不细讲
    struct SegmentTree//线段树
    {
    	int num;
    	LazyTag tag;
    }sgt[2][maxN*4];//奇偶两颗线段树
    #define LSON (now<<1)
    #define RSON (now<<1|1)
    #define MIDDLE ((left+right)>>1)
    #define LEFT LSON,left,MIDDLE
    #define RIGHT RSON,MIDDLE+1,right
    #define NOW team,now_left,now_right,add
    //想每个函数中的第一个参数team表示修改的是哪一颗线段树
    void Build(int team,int now,int left,int right)//建树
    {
    	if(left==right)
    	{
    		sgt[team][now].num=val[dfs[team][left]];
    		return;
    	}
    	Build(team,LEFT);
    	Build(team,RIGHT);
    }
    void Down(int team,LazyTag tag,int now,int left,int right)//修改部分
    {
    	sgt[team][now].tag.add+=tag.add;
    	if(left==right)//只有叶节点需要修改值
    	{
    		sgt[team][now].num+=tag.add;
    	}
    }
    void PushDown(int team,int now,int left,int right)//下传懒标记
    {
    	if(sgt[team][now].tag.add)
    	{
    		Down(team,sgt[team][now].tag,LEFT);
    		Down(team,sgt[team][now].tag,RIGHT);
    		sgt[team][now].tag.CleanLazyTag();
    	}
    }
    void Updata(int team,int now_left,int now_right,int add,int now,int left,int right)//修改部分
    {
    	if(now_right<left||right<now_left)
    	{
    		return;
    	}
    	if(now_left<=left&&right<=now_right)
    	{
    		Down(team,MakeLazyTag(add),now,left,right);
    		return;
    	}
    	PushDown(team,now,left,right);
    	Updata(NOW,LEFT);
    	Updata(NOW,RIGHT);
    }
    int Query(int team,int place,int now,int left,int right)//查询
    {
    	if(right<place||place<left)
    	{
    		return 0;
    	}
    	if(left==right)
    	{
    		return sgt[team][now].num;
    	}
    	PushDown(team,now,left,right);
    	return Query(team,place,LEFT)+Query(team,place,RIGHT);
    }
    #undef LSON
    #undef RSON
    #undef MIDDLE
    #undef LEFT
    #undef RIGHT
    #undef NOW
    #define TEAM (deep[u]&1)
    int main()
    {
    	scanf("%d%d",&N,&M);
    	REP(i,1,N)
    	{
    		scanf("%d",&val[i]);
    	}
    	int father,son;
    	REP(i,1,N-1)
    	{
    		scanf("%d%d",&father,&son);
    		AddEdge(father,son);//连边
    		AddEdge(son,father);
    	}
    	deep[1]=1;
    	DFS();//BFS
    	//注意建树前需要判断树中是否有节点
    	if(dfs_cnt[0])
    	{
    		Build(0,1,1,dfs_cnt[0]);
    	}
    	if(dfs_cnt[1])
    	{
    		Build(1,1,1,dfs_cnt[1]);
    	}
    	int opt,u,add_val;
    	REP(i,1,M)
    	{
    		scanf("%d",&opt);
    		if(opt==1)
    		{
    			scanf("%d%d",&u,&add_val);
    			Updata(TEAM,place[u],place[u]+size[TEAM][u]-1,add_val,1,1,dfs_cnt[TEAM]);//修改奇偶性和这个点一样的部分
    			if(first_son[u])
    			{
    				Updata(TEAM^1,place[first_son[u]/*记录第一个子节点的左右*/],place[first_son[u]]+size[TEAM^1][u]-1,-add_val,1,1,dfs_cnt[TEAM^1]);//奇偶性不同的部分
    			}
    		}
    		if(opt==2)//查询和普通线段树差不多
    		{
    			scanf("%d",&u);
    			printf("%d
    ",Query(TEAM,place[u],1,1,dfs_cnt[TEAM]));
    		}
    	}
    	return 0;
    }
    #undef TEAM
    
  • 相关阅读:
    学习MyBatis时报的错
    Day01
    PAT乙级01
    基于python-django框架的支付宝支付案例
    单线程与多线程的应用 --Python3
    Python异常 --Python
    有四个数字能组成多少个互不相同的三位数 --Python
    with as用法 --Python
    采用霍夫曼编码(Huffman)画出字符串各字符编码的过程并求出各字符编码 --多媒体技术与应用
    函数和代码复用 --Python
  • 原文地址:https://www.cnblogs.com/Sxy_Limit/p/12331430.html
Copyright © 2020-2023  润新知