前言:
清冬上不会线段树合并又没想到启发式合并亏了50+……
题目大意:
给出一个一棵以1为根的树。
每个点有一个颜色。
每次询问给出x,d,为以x为根的子树中,到x距离小于等于d的点有多少种不同的颜色。
题解:
感觉这题还是个线段树合并的裸题吧(也没有这么裸)。
先谈谈自己对线段树合并的感受。
线段树应该都得是动态开点的线段树,不然合并不就是O(n)的吗?
给出一段伪代码。
merge(&i,x,y)
//i是合并后存到哪里,x,y是两个线段的某个点,注意x,y代表的区间是完全一样的。
if(!x) i = y,return;
if(!y) i = x,return;
//如果有一个是空,就直接返回另一个
i = ++tot;
//否则开个新的点来存。
merge(a[i].l, a[x].l, a[x].r);
merge(a[i].r, a[x].r, a[y].r);
//合并子节点
最后维护你该维护的东西。
For example:
a[i].s = a[a[i].l].s +a[a[i].r].s;
复杂度怎么分析呢?
我也不会,网上说均摊是O(n log n)的,常数巨大。
所以《GDOI2017 取石子游戏》那题,如果直接线段树合并,10^6就hhh了。(千万不要学数据结构学傻了)
再来看这题。
先只思考个数,不考虑颜色的重复,直接线段树合并,没毛病。
可是发现颜色的重复并不好去,离线是可以做。
看了网上题解,强制在线的话,还是用牛逼的线段树合并,做法是再来一棵线段树,记录的是某颜色是否出现在这个线段树代表的子树里。
合并线段树时,也合并一下这棵线段树,如果某颜色出现在两棵线段树的话,则当前答案要减一。
总复杂度是O(n log n)
5e5竟然跑了2s,恐怖的常数!
#include<cstdio>
#include<cstring>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;
const int N = 5e5 + 5;
int T, n, m, x, d, ans, fa[N], c[N], dep[N];
int next[N], to[N], final[N], tot;
int r1[N], r2[N];
void link(int x, int y) {
next[++ tot] = final[x], to[tot] = y, final[x] = tot;
}
int td;
struct node {
int l, r, s;
} a[N * 100];
void add(int &i, int x, int y, int l, int c) {
a[++ td] = a[i]; i = td;
if(x == y) { a[i].s += c; return;}
int m = x + y >> 1;
if(l <= m) add(a[i].l, x, m, l, c); else add(a[i].r, m + 1, y, l, c);
a[i].s = a[a[i].l].s + a[a[i].r].s;
}
void bin(int &i, int x, int y, int pos) {
if(!x) {i = y; return;}
if(!y) {i = x; return;}
i = ++ td;
if(!pos) a[i].s = a[x].s + a[y].s;
else if (!a[x].l && !a[x].r) {
a[i].s = min(a[x].s, a[y].s);
add(r1[pos], 1, n, max(a[x].s, a[y].s), -1);
}
bin(a[i].l, a[x].l, a[y].l, pos);
bin(a[i].r, a[x].r, a[y].r, pos);
}
int find(int i, int x, int y, int l, int r) {
if(l == x && r == y) return a[i].s;
int m = x + y >> 1;
if(r <= m) return find(a[i].l, x, m , l, r); else
if(l > m) return find(a[i].r, m + 1, y, l, r); else
return find(a[i].l, x, m, l, m) + find(a[i].r, m + 1, y, m + 1, r);
}
void dg(int x) {
add(r1[x], 1, n, dep[x], 1);
add(r2[x], 1, n, c[x], dep[x]);
for(int i = final[x]; i; i = next[i]) {
int y = to[i];
dep[y] = dep[x] + 1;
dg(y);
bin(r1[x], r1[x], r1[y], 0);
bin(r2[x], r2[x], r2[y], x);
}
}
void read(int &x) {
x = 0; char ch = ' ';
for(; ch < '0' || ch > '9';) ch = getchar();
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - 48;
}
int main() {
for(scanf("%d", &T); T; T --) {
fo(i, 1, td) a[i].s = a[i].l = a[i].r = 0;
fo(i, 1, tot) next[i] = 0;
fo(i, 1, n) final[i] = r1[i] = r2[i] = 0;
td = tot = 0;
scanf("%d %d", &n, &m);
fo(i, 1, n) read(c[i]);
fo(i, 2, n) read(fa[i]), link(fa[i], i);
dep[1] = 1; dg(1);
ans = 0;
for(; m; m --) {
scanf("%d %d", &x, &d);
x ^= ans; d ^= ans;
ans = find(r1[x], 1, n, dep[x], min(dep[x] + d, n));
printf("%d\n", ans);
}
}
}

本文介绍了一道关于线段树合并的问题,通过实例讲解如何利用线段树解决树形结构中涉及颜色计数的问题,并提供了完整的代码实现。
522

被折叠的 条评论
为什么被折叠?



