HDU 4836 —— The Query on the Tree(线段树+LCA)

3 篇文章 0 订阅
1 篇文章 0 订阅

题目:http://acm.hdu.edu.cn/showproblem.php?pid=4836

下午百度之星复赛里最简单的一题,虽然我还是1个小时才AC的,呃,下午果断被虐粗翔。

本题磨了1个小时才过,第1题还是很猥琐地用了随机数过的(真不知道怎么做)。

回到这题来,其实也不知道大牛们怎么做的,我只能用线段树+LCA搞了。

首先考虑根不改变的情况,那么我们可以将每个结点映射到线段树上去,让每个点对应的子树的点都落在某个连续区间上;

具体是在dfs的时候,用id来表示当前已经映射到线段树的编号,初始为0,进入某个结点X,令left[X]=right[X]=++id,然后继续dfs,对于X每个子节点j,dfs完了顺便更新right[X]=max(right[X], right[j]),这样在所有子节点遍历完的情况下,left[X]到right[X]就刚好是以X为根的子树的区间。在纸上模拟下就明白了。

然后对于查询和点更新就是个经典的线段树问题了。

比较麻烦的是根被改变的情况,假设我们现在要查询以X为根的子树的和,分析下当前根是root时与原先的不同。如果root原来就是X的祖先了,那么答案跟原来是一样的,再往下想其实就是只要root需要经过X原先某个祖先到达X的话,答案应该都一样的,所以这个好办。

而如果X刚好就是root,那么答案就是整棵树的和,也好处理。

剩下的就是原来X是root的祖先,我们假设X经过它的某个子节点Y到达root,那么很明显当前X的子树和,应该是整棵树的值减去原来Y对应的子树的和。因为X-Y这条边是root进行dfs到达X的最后一条边,换句话说这条边就把是不是X的子树的点分隔开了。

分析到这里问题就好办了,对于查询X,求出它们的最近公共祖先V,再进行判断即可。

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
#define lson o<<1
#define rson (o<<1)|1
#define N 10001
#define pb push_back
vector<int> V[N];
int t, ct, n;
int left[N], right[N], val[N], v2[N];
int s[N<<2], l[N<<2], r[N<<2];
int id, root;
bool f[N];
int depth[N], parent[16][N];
void dfs(int x){
    left[x] = right[x] = ++id;
    v2[id] = val[x];
    for(int i=0; i<V[x].size(); i++){
        int j=V[x][i];
        if(f[j])    continue;
        f[j]=1;
        depth[j] = depth[x]+1;
        parent[0][j] = x;
        dfs(j);
        right[x] = max(right[x], right[j]);
    }
}
void maintain(int o){
    s[o] = s[lson]+s[rson];
}
void build(int o, int ll, int rr){
    l[o]=ll; r[o]=rr;
    s[o]=0;
    if(ll<rr){
        int m = (ll+rr)>>1;
        build(lson, ll, m);
        build(rson, m+1, rr);
        maintain(o);
    }
    else{
        s[o] = v2[ll];
    }
}
void update(int o, int p, int v){
    if(l[o]==p && r[o]==p){
        s[o]=v;
        return;
    }
    int m = (l[o]+r[o])>>1;
    if(p<=m)    update(lson, p, v);
    else    update(rson, p, v);
    maintain(o);
}
void init_lca(){
    for(int k=0; k<15; k++){
        for(int v=1; v<=n; v++){
            if(parent[k][v]<0)  parent[k+1][v]=-1;
            else    parent[k+1][v] = parent[k][parent[k][v]];
        }
    }
}
int lca(int u, int v){
    if(depth[u]>depth[v]){
        swap(u,v);
    }
    for(int k=0; k<16; k++){
        if((depth[v]-depth[u])>>k & 1){
            v = parent[k][v];
        }
    }
    if(u==v)    return u;
    for(int k=15; k>=0; k--){
        if(parent[k][u]!=parent[k][v]){
            u=parent[k][u];
            v=parent[k][v];
        }
    }
    return parent[0][u];
}
int query(int o, int ll, int rr){
    if(l[o]==ll && r[o]==rr)    return s[o];
    int m = (l[o]+r[o])>>1;
    if(rr<=m)   return query(lson, ll, rr);
    else if(ll>m)   return query(rson, ll, rr);
    else    return query(lson, ll, m)+query(rson, m+1, rr);
}
int query(int x){
    if(x==root) return s[1];
    int v = lca(root, x);
    if(v!=x){
        return query(1, left[x], right[x]);
    }
    for(int i=0; i<V[x].size(); i++){
        int j=V[x][i];
        if(depth[j]<depth[x])   continue;
        v = lca(root, j);
        if(v==j){//找到前面所说的Y
            return s[1] - query(1, left[j], right[j]);
        }
    }
    return 0;
}
inline void in(int &x){
    char c=getchar();
    x=0;
    while(c<48 || c>57) c=getchar();
    while(c>=48 && c<=57){
        x = x*10+c-48;
        c = getchar();
    }
}
int main(){
    in(t);
    for(ct=1; ct<=t; ct++){
        printf("Case #%d:\n", ct);
        in(n);
        for(int i=1; i<=n; i++) V[i].clear();
        int x, y;
        for(int i=1; i<n; i++){
            in(x); in(y);
            V[x].pb(y);
            V[y].pb(x);
        }
        for(int i=1; i<=n; i++){
            in(val[i]);
        }
        memset(f,0,sizeof(f));
        memset(parent,-1,sizeof(parent));
        f[1]=1;
        id = 0;
        depth[1]=1;
        dfs(1);
        init_lca();
        build(1, 1, n);
        int q;
        char op[10];
        in(q);
        root=1;
        while(q--){
            scanf("%s", op);
            in(x);
            if(op[0]=='Q'){
                printf("%d\n", query(x));
            }
            else if(op[0]=='C'){
                in(y);
                update(1, left[x], y);
            }
            else{
                root = x;
            }
        }
    }
    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值