[bzoj1036]树的统计

15 篇文章 0 订阅
10 篇文章 0 订阅

题目描述

 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。
  我们将以下面的形式来要求你对这棵树完成一些操作:
  I. CHANGE u t : 把结点u的权值改为t
  II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值
  III. QSUM u v: 询问从点u到点v的路径上的节点的权值和
  注意:从点u到点v的路径上的节点包括u和v本身
  对于100%的数据,保证1<=n<=30000,0<=q<=200000;中途操作中保证每个节点的权值w在-30000到30000之间。

一眼树链剖分

显然,这是一道关于维护树中路径的题目,一眼树链剖分。本弱并没有打树链剖分解法。

作死的动态树

强行把这道题打成动态树也是可以的。。。
代买如下:

#include<cstdio>
#include<algorithm>
#include<stack>
#include<cstring>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=30000+10;
stack<int> sta;
struct dong{
    int x,y;
};
dong e[maxn];
int tree[maxn][2],key[maxn],father[maxn],num[maxn],sum[maxn],pp[maxn];
int i,j,k,l,t,n,m;
bool bz[maxn];
char ch;
int pd(int x){
    if (tree[father[x]][0]==x) return 0;else return 1;
}
void update(int x){
    num[x]=max(key[x],max(num[tree[x][0]],num[tree[x][1]]));
    sum[x]=sum[tree[x][0]]+sum[tree[x][1]]+key[x];
}
void rotate(int x){
    int y=father[x],z=pd(x);
    tree[y][z]=tree[x][1-z];
    if (tree[x][1-z]) father[tree[y][z]]=y;
    father[x]=father[y];
    if (father[y]) tree[father[y]][pd(y)]=x;
    father[y]=x;
    tree[x][1-z]=y;
    update(y);
    update(x);
    if (pp[y]) pp[x]=pp[y],pp[y]=0;
}
void clear(int x){
    if (bz[x]){
        bz[x]=0;
        if (tree[x][0]) bz[tree[x][0]]^=1;
        if (tree[x][1]) bz[tree[x][1]]^=1;
        swap(tree[x][0],tree[x][1]);
    }
}
void romove(int x,int y){
    while (x!=y){
        sta.push(x);
        x=father[x];
    }
    while (!sta.empty()){
        clear(sta.top());
        sta.pop();
    }
}
void splay(int x,int y){
    romove(x,y);
    while (father[x]!=y){
        if (father[father[x]]!=y)
            if (pd(x)==pd(father[x])) rotate(x);else rotate(father[x]);
        rotate(x);
    }
}
void access(int x){
    int y;
    splay(x,0);
    father[tree[x][1]]=0;
    if (tree[x][1]) pp[tree[x][1]]=x;
    tree[x][1]=0;
    update(x);
    while (pp[x]!=0){
        y=pp[x];
        splay(y,0);
        father[tree[y][1]]=0;
        if (tree[y][1]) pp[tree[y][1]]=y;
        tree[y][1]=x;
        father[x]=y;
        pp[x]=0;
        update(y);
        splay(x,0);
    }
}
void makeroot(int x){
    access(x);
    splay(x,0);
    bz[x]^=1;
}
void link(int x,int y){
    makeroot(x);
    access(y);
    splay(x,0);
    pp[x]=y;
    access(x);
}
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
char get(){
    char ch=getchar();
    while (ch<'A'||ch>'Z') ch=getchar();
    return ch;
}
int main(){
    num[0]=-10000000;sum[0]=0;
    scanf("%d",&n);
    fo(i,1,n-1) scanf("%d%d",&e[i].x,&e[i].y);
    fo(i,1,n){
        scanf("%d",&key[i]);
        num[i]=sum[i]=key[i];
    }
    fo(i,1,n-1) link(e[i].x,e[i].y);
    scanf("%d",&m);
    while (m--){
        if (get()=='C'){
            j=read();k=read();
            splay(j,0);
            key[j]=k;
            update(j);
        }
        else{
            ch=get();
            j=read();k=read();
            makeroot(j);
            access(k);
            splay(k,0);
            if (ch=='M') printf("%d\n",num[k]);else printf("%d\n",sum[k]);
        }
    }
}

脑洞大开

这道题已经被解决了,那我们来脑洞大开一下:
现在本题去掉修改操作,数据范围都多加个0,该怎么做?
我的想法是离线求出每个询问的lca,然后把询问挂在那。
然后递归处理,对于x,先递归处理x的每棵子树,然后现在我们有两颗线段树,第一颗线段树维护dfn序的区间和,第二个维护区间max。那么对于点i,现在保存的就是其到其最远被处理完的祖先的距离和与距离max,在线段树的dfn[i]位置上。
现在扫描到x的一个子树,递归处理其后,对其对应的线段树区间做修改(第一颗打add标记,第二颗打max标记)。
如果x上有询问,显然可以解决。
复杂度n log n。
然而显然我们可以用并查集维护而不是线段树就变成近乎o(n)了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值