树的统计

2 篇文章 0 订阅
1 篇文章 0 订阅

题目大意

一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。要求你对这棵树完成
一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 I
II. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身

数据范围 1<=n<=30000,0<=q<=200000;中途操作中保证每个节点的权值w在-30000到30000之间。

1树链剖分(相对简单,不打算讲)。

2动态树

用splay维护max值和sum值。
对于查询操作u到v路径的max值或sum值 u<vswap(u,v))
先access(u),再access(v),access(v)的过程中最后一个维护的点即是lca(u,v),因为access(u)后,u和lca已在同一tree中,且树的父亲(树中深度最小的点的父亲)为null,
所以access(v)处理到lca后便会结束。求max值,答案max(max[u],key[lca],max[tree[lca][1]]),因为执行access(v)后,u和lca的树边便会断开,又u是树中深度最大的点,所以max[u]是u到lca的路径(不包括lca)中的max值,
同理可得max[tree[lca][1]]是lca到v的路径(不包括lca)中的max值。
求sum值,类似的做法。
修改操作,先access(u),把u旋到树顶,然后修改w[u]=t。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<climits>
#define inf INT_MAX
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=30000+5;
int tree[maxn][2],fa[maxn],pfa[maxn],key[maxn],num[maxn],sum[maxn],lca,next[maxn*2],f[maxn],g[maxn*2],cnt,d[maxn];
int i,j,n,m;
void add(int x,int y){
    next[++cnt]=f[x];
    f[x]=cnt;g[cnt]=y;
}
void update(int x){
    num[x]=max(key[x],max(num[tree[x][0]],num[tree[x][1]]));
    sum[x]=key[x]+sum[tree[x][0]]+sum[tree[x][1]];
}
int pd(int x){
    if (tree[fa[x]][0]==x) return 0;else return 1;
}
void rotate(int x){
    int y=fa[x],z=pd(x);
    fa[x]=fa[y];
    if (fa[y]) tree[fa[y]][pd(y)]=x;else
    pfa[x]=pfa[y],pfa[y]=0;
    tree[y][z]=tree[x][1-z];
    if (tree[x][1-z]) fa[tree[x][1-z]]=y;
    tree[x][1-z]=y;
    fa[y]=x;
    update(y);
}
void splay(int x){
    while (fa[x]!=0){
        int y=fa[x];
        if (fa[y]!=0) 
        if (pd(x)==pd(y)) rotate(y);else rotate(x);
        rotate(x);
    }
    update(x);
}
void access(int u){
    int v=0;
    while (u){
        splay(u);
        fa[tree[u][1]]=0;pfa[tree[u][1]]=u;
        tree[u][1]=v;fa[v]=u;
        pfa[v]=0;
        update(u);
        v=u,u=pfa[u];
    }lca=v;
}
void ch(int x,int y){
    access(x);
    splay(x);
    pfa[x]=y;
}
void dfs(int x,int y){
    int i=f[x];
    while (i>0){
        if (g[i]!=y) ch(g[i],x),d[g[i]]=d[x]+1,dfs(g[i],x);
        i=next[i];
    }
}
int main(){
    key[0]=num[0]=-inf;
    scanf("%d",&n);
    fo(i,1,n-1) {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y),add(y,x);
    }dfs(1,0);
    fo(i,1,n) scanf("%d",&key[i]);
    scanf("%d",&m);
    fo(i,1,m){
        char c=getchar();int x,y;
        while ((c<'A')||(c>'Z')) c=getchar();
        if (c=='Q') {
            fo(j,1,3) c=getchar();
            scanf("%d%d",&x,&y);
            if (d[x]<d[y]) swap(x,y);
            if (x==y){
                printf("%d\n",key[x]);
                continue;
            }
            access(x);
            lca=y,access(y);
            splay(x);
            int ans;
            if (c=='X'){
                ans=num[x];
                if (lca==y) ans=max(ans,key[y]);else 
                ans=max(ans,max(key[lca],num[tree[lca][1]]));
            }else{
                ans=sum[x];
                if (lca==y) ans=ans+key[y];else 
                ans=ans+key[lca]+sum[tree[lca][1]];
            }
            printf("%d\n",ans);
        }else{
            fo(j,1,5) c=getchar();
            scanf("%d%d",&x,&y);
            access(x);
            splay(x);
            key[x]=y;
            update(x);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值