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

来源:互联网 发布:php留言板视频教程 编辑:程序博客网 时间:2024/05/01 01:31
#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|1typedef 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]子树有权值为1Root[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)*/

0 0
原创粉丝点击