【2016杭电女生赛1008】【数据结构 动态节点线段树】Claris Loves Painting

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; }
const int N = 1e5 + 10;
const int M = 1e7; //一个问题是,我们需要多少个节点呢?(n+m)*log(n)*3 about?
int casenum, casei;
int n, m, x, d;
struct Node
{
	int l, r, v;
}node[M]; int id;
vector<int>a[N];
int c[N];
int dep[N];
int root[N];	//root[x]表示以x为子树根节点的线段树,其线段树用于维护每个深度的颜色数
int Root[N];	//Root[x]表示以x为子树根节点的线段树,其线段树用于维护每个每个颜色对应的最小深度

//该函数用于在原有线段树基础上,实现v[p]+=val操作
int update(int o, int l, int r, int p, int val)
{
	int now = ++id;
	node[now] = node[o];
	node[now].v += val;
	if (l == r)return now;
	int mid = (l + r) >> 1;
	if (p <= mid)node[now].l = update(node[o].l, l, mid, p, val);
	else node[now].r = update(node[o].r, mid + 1, r, p, val);
	return now;
}

//该函数用户合并两棵线段树,对于深度-颜色数线段树,可以维护使得数量累加;对于颜色-深度线段树,
int merge(int x, int y, int pos=0)
{
	//合并原则1,如果有子树为空可以直接合并,并不需要做修改
	if (!x && !y)return 0;
	if (!x)return y;
	if (!y)return x;

	//合并原则2,两棵子树均非空时,我们需要新建节点做合并
	int now = ++id;
	if (!pos)node[now].v = node[x].v + node[y].v;
	else if (!node[x].l && !node[x].r)										//如果涉及到颜色-深度线段树的合并(即pos不为0),那么我们只有在叶子节点才做删除操作
	{																		//具体的删除操作,是找到深度较大的颜色,把其在深度-数量线段树中删除
		node[now].v = min(node[x].v, node[y].v);
		root[pos] = update(root[pos], 1, n, max(node[x].v, node[y].v), -1);
	}
	node[now].l = merge(node[x].l, node[y].l, pos);
	node[now].r = merge(node[x].r, node[y].r, pos);
	return now;
}

void dfs(int x)
{
	root[x] = update(0, 1, n, dep[x], 1);		//先在空线段树基础上建立深度-数量线段树,初始化只有dep[x]子树有权值为1
	Root[x] = update(0, 1, n, c[x], dep[x]);	//然后在空线段树基础上建立颜色-最小深度线段树(该线段树只为了维护叶节点,并没有区间效应),初始化只有颜色c[x]的值为深度dep[x]
	for (int i = a[x].size() - 1; ~i; --i)
	{
		int y = a[x][i];
		dep[y] = dep[x] + 1;
		dfs(y);
		root[x] = merge(root[x], root[y]);		//我们要合并深度-数量线段树,可以直接通过简单的数值累加做合并
		Root[x] = merge(Root[x], Root[y], x);	//然后我们要通过颜色-深度线段树的查询,对深度-数量线段树做修订
	}
}

void init()
{
	node[ id = 0 ].v = node[0].l = node[0].r = 0;
	dep[1] = 1;
	dfs(1);
}

int L, R;
int query(int o, int l, int r)
{
	if (!o)return 0;
	if (l >= L&&r <= R)return node[o].v;
	int mid = (l + r) >> 1;
	int ans = 0;
	if (L <= mid)ans += query(node[o].l, l, mid);
	if (R > mid)ans += query(node[o].r, mid + 1, r);
	return ans;
}
int main()
{
	scanf("%d", &casenum);
	for (casei = 1; casei <= casenum; ++casei)
	{
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; ++i)scanf("%d", &c[i]), a[i].clear();
		for (int i = 2; i <= n; ++i)scanf("%d", &x), a[x].push_back(i);
		init();
		int pre = 0;
		for (int i = 1; i <= m; ++i)
		{
			scanf("%d%d", &x, &d);
			x ^= pre; d ^= pre;
			L = dep[x]; R = min(n, dep[x] + d);
			printf("%d\n", pre = query(root[x], 1, n));
		}
	}
	return 0;
}
/*
【trick&&吐槽】
LCY:csy啊,我们需要一道中等偏上的数据结构体,你出简单点。
csy:嗯。

【题意】
给你一棵n(1e5)个节点的树
每个节点都有一个颜色
有m(1e5)个询问
对于每个询问,
问你以一个节点为子树的根,距离其深度<=d的所有节点中,有多少种不同颜色。
强制在线。

【类型】
动态节点线段树

【分析】
首先,这道题的解题思想是这样的——
我们做dfs,然后自底向上,维护两个东西。
1,每个颜色对应的最小深度
2,每个深度对应的颜色数

然后,我们对于一个新节点(显然处理这个节点的时候,这个节点是位于父节点的结构位置的)
我们首先,给它建立一棵线段树。在这棵线段树是基于全0线段树展开的。
唯一不同的是,在dep[x]的位置,权值要为1。
于是我们按照这个原理,在不超过logn的复杂度内建立一棵线段树。

然后,我们要涉及到其与子节点的合并操作。
怎么合并的呢?如果有一棵子树为空,那么显然我们直接扔掉。
否则我们就合并左子树,合并右子树。
因为这个是维护深度->节点数,
所以我们合并的时候,直接把节点数相加就好。

我们这样就能够得到:以每个节点为根的子树中,各个深度的颜色数。
然而,在这个条件下,我们的计数是可能发生重复的。就是同一个颜色,被计数两次。
如何维护呢?
我们对于一种颜色,只要维护该颜色的最小深度即可。
这个同样可以用一棵线段树来实现。
也去建一棵线段树,这棵线段树也是基于全0线段树展开的。
只是,在颜色c[x]的位置,权值要为其深度。

这棵线段树虽然说是线段树,然而实际上,其实只是为了维护叶节点而已。
虽然我们看似是一直查找所有的叶节点,
然而,一旦是空子树我们就不向下查找了。

【时间复杂度&&优化】
维护深度-节点数的复杂度是O(nlogn),因为我们每次修改深度
查找叶节点的复杂度为log级别,叶节点的数量为n级别,合并操作复杂度为O(1)
因此总复杂度为O(nlogn)

*/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值