线段树合并——hdu 5709 Claris Loves Painting

前言:

清冬上不会线段树合并又没想到启发式合并亏了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);
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值