E. Danil and a Part-time Job(Codeforces-877E)(dfs序+线段树)

题目:

Danil decided to earn some money, so he had found a part-time job. The interview have went well, so now he is a light switcher.

Danil works in a rooted tree (undirected connected acyclic graph) with n vertices, vertex 1 is the root of the tree. There is a room in each vertex, light can be switched on or off in each room. Danil's duties include switching light in all rooms of the subtree of the vertex. It means that if light is switched on in some room of the subtree, he should switch it off. Otherwise, he should switch it on.

Unfortunately (or fortunately), Danil is very lazy. He knows that his boss is not going to personally check the work. Instead, he will send Danil tasks using Workforces personal messages.

There are two types of tasks:

  1. pow v describes a task to switch lights in the subtree of vertex v.
  2. get v describes a task to count the number of rooms in the subtree of v, in which the light is turned on. Danil should send the answer to his boss using Workforces messages.

A subtree of vertex v is a set of vertices for which the shortest path from them to the root passes through v. In particular, the vertex v is in the subtree of v.

Danil is not going to perform his duties. He asks you to write a program, which answers the boss instead of him.

Input

The first line contains a single integer n (1 ≤ n ≤ 200 000) — the number of vertices in the tree.

The second line contains n - 1 space-separated integers p2, p3, ..., pn (1 ≤ pi < i), where pi is the ancestor of vertex i.

The third line contains n space-separated integers t1, t2, ..., tn (0 ≤ ti ≤ 1), where ti is 1, if the light is turned on in vertex i and 0 otherwise.

The fourth line contains a single integer q (1 ≤ q ≤ 200 000) — the number of tasks.

The next q lines are get v or pow v (1 ≤ v ≤ n) — the tasks described above.

Output

For each task get v print the number of rooms in the subtree of v, in which the light is turned on.

Example

Input

4
1 1 1
1 0 0 1
9
get 1
get 2
get 3
get 4
pow 1
get 1
get 2
get 3
get 4

Output

2
0
0
1
2
1
1
0

Note

 The tree before the task pow 1.

 The tree after the task pow 1.

题意

其实对于线段树就两种操作,一种是

get x :就是求以x为根的子树上亮灯的节点数

pow x :就是变化以x为根的子树的灯,开着的就关掉,关着的就打开

再一个要看懂给你的图,1 1 1(样例第二行)表示从2节点开始的节点i,的父节点是ai,就是这些1,代表2,3,4节点的父亲都是1节点

思路(dfs序+线段树)

因为涉及到了处理一棵树的子树问题,对他进行求和或者修改,所以我们可以用dfs序来做,把树状的数据变成线性的,然后使用线段树进行处理

什么是dfs序,大家可以看我的https://blog.csdn.net/weixin_44554979/article/details/100046311这篇博客

然后这个题有点特殊就是对于一个节点的孩子以及这个节点里面的灯都发生变化,不过如果变化两次的话就相当于没改变!

然后大家分几部分看一下代码

首先是dfs序部分

void dfs(int root,int fa)//root是此时的节点,fa是这个节点的父亲,其实这是为了避免重复走
{
	
	in[root]=++time;//进入的时间点
	
	for(int i=0;i<nex[root].size();i++)
	{
		if(nex[root][i]==fa)
		{
			continue;
		}
		dfs(nex[root][i],root);
	}
	out[root]=time;//出去的时间点,好好想想递归的过程,正是因为time没有++所以有可能几个点的出去时间相同
}

AC代码(有注释)

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include<stdio.h>
#include<string.h>
#include<limits.h>
#include<vector>
#include<queue>
#include<map>
using namespace std;
const int maxn=203005;

int n,m,time;
//vector<int>v[maxn*2];
typedef vector<int>v;//就是vector建树,和上面注释掉的其实一样
 vector<v>nex(maxn*2);

int in[maxn],out[maxn],num[maxn];
int book[maxn];
int ba[maxn];
int ha[maxn];
void add(int b1,int b2)
{

	nex[b1].push_back(b2);
	
}
void dfs(int root,int fa)
{
	//ba[root]=fa;
	in[root]=++time;
	num[time]=root;
	for(int i=0;i<nex[root].size();i++)
	{
		if(nex[root][i]==fa)
		{
			continue;
		}
		dfs(nex[root][i],root);
	}
	out[root]=time;
}

struct tree{
    int v;
    int lazy;//标记这个节点是否更改,和普通线段树没什么区别 
	
}a[maxn<<2];
void push_up(int root)
{
	a[root].v=a[root<<1].v+a[root<<1|1].v;
}
void push_down(int root,int l,int r)//下传标记,得多想啊 
{
	if(a[root].lazy%2!=0)//为什么%2因为lazy=0与是2的倍数是一样的道理,因为变换两次相当于没变 
	{
		a[root<<1].lazy+=a[root].lazy;//下传标记 
		a[root<<1|1].lazy+=a[root].lazy; 
		if(a[root<<1].lazy%2==0) //如果此时==0说明原来root<<1的lazy是以1,说明这个点已经改了 
		{
			a[root<<1].v=r-l+1-(r-l+1)/2-a[root<<1].v;//此时还需要改一次 
			a[root<<1].lazy=0;//但是这个点下面的点不需要再改了,改两次相当于没改 
		}
		else{
			a[root<<1].v=r-l+1-(r-l+1)/2-a[root<<1].v;//说明原来 root<<1的lazy是0,不需要修改
			//但现在是1了,所以要修改 
		}
		if(a[root<<1|1].lazy%2==0)//同理 
		{
			a[root<<1|1].v=(r-l+1)/2-a[root<<1|1].v;
			a[root<<1|1].lazy=0;
		}
		else{
			a[root<<1|1].v=(r-l+1)/2-a[root<<1|1].v;
		}
		
	}
	a[root].lazy=0;//不管原来是1还是0反正最后都是0 
}
void build(int l,int r,int root)//建树 
{
	if(l==r)
	{
		a[root].v=ha[l];//看看main函数,好好想想,是两个树 
		a[root].lazy=0;
		return ;
	}
	int mid=(l+r)>>1;
	build(l,mid,root<<1);
	build(mid+1,r,root<<1|1);
	push_up(root);
}
void update(int l,int r,int root,int la,int ra)
{
	if(la<=l&&ra>=r)
	{
		a[root].lazy+=1;
		
		a[root].v=r-l+1-a[root].v;//要变换就是反过来呗 
	
		return ;
	}
	push_down(root,l,r);
	int mid=(l+r)>>1;
	if(la<=mid)
	{
		update(l,mid,root<<1,la,ra);
	}
	if(ra>mid)
	{
		update(mid+1,r,root<<1|1,la,ra);
	}
	push_up(root);
}
int query(int l,int r,int root,int la,int ra)//和普通线段树的一样 
{
	if(la<=l&&ra>=r)
	{
		return a[root].v;
	}
	push_down(root,l,r);
	int mid=(l+r)>>1;
	int ans=0;
	if(la<=mid)
	{
	ans+=query(l,mid,root<<1,la,ra);
	}
	if(ra>mid)
	{
		ans+=query(mid+1,r,root<<1|1,la,ra);
	}
	return ans;
}


int main()
{
	int i,j,a1,a2;
	char ch[20];
	time=0;
	scanf("%d",&n);
	for(i=2;i<=n;i++)
	{
		scanf("%d",&a1);
		add(a1,i);//建图 
	}
	dfs(1,-1);//处理dfs序 

	for(i=1;i<=n;i++)
	{
		scanf("%d",&a1);
		ha[in[i]]=a1;//读取灯的状态,对于样例就是读取 第三行的1 0 0 1 
	}//之所以in[i]   i表示第i个节点(题目的树),但我们线段树 处理的是in时间段组成的序列 
	build(1,n,1);建树 
	scanf("%d",&m);//读取这几种操作 
	for(i=1;i<=m;i++)
	{
		scanf("%s%d",ch,&a2);
		if(ch[0]=='g')
		{
			printf("%d\n",query(1,n,1,in[a2],out[a2]));//查询 
		}
		else{
			update(1,n,1,in[a2],out[a2]);//对灯进行变换 
		}
		
	}
	
	return 0;
}
	

难点解析

我觉得最难以理解的就是此题不要把两个树混了,我们在处理线段树,这个线段树的叶子节点是什么,我们要明白,是dfs序处理以后的in时间点

再一个是线段树的push_down函数,也就是懒惰标记这个地方,记住,如果lazy=1了,表示这个点已按要求修改,只不过它的孩子还未修改,如果有个查询查的是这个节点往上的,即他的祖先,那答案直接查询就可,此时也是正确的,因为update函数已经直接修改了(看下面代码红色的部分),往上的节点又通过push_up函数修改总结,所以祖先都是正确的

void update(int l,int r,int root,int la,int ra)
{
    if(la<=l&&ra>=r)
    {
        a[root].lazy+=1;
        
        a[root].v=r-l+1-a[root].v;//要变换就是反过来呗
    
        return ;
    }
    push_down(root,l,r);
    int mid=(l+r)>>1;
    if(la<=mid)
    {
        update(l,mid,root<<1,la,ra);
    }
    if(ra>mid)
    {
        update(mid+1,r,root<<1|1,la,ra);
    }
    push_up(root);
}

不过要是查询这个节点下面的孩子,但是还没修改,所以要下沉标记,就是push_down函数作用

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值