BZOJ 3531 旅行 (树链剖分 + 线段树动态开点)

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3531

题目大意:中文题就不解释题意了~(-,-)

题目思路:这种树上两点链上查询的题目一眼就是树链剖分了,但是由于是要查询一条链上相同宗教的值,所以就不好用普通的线段树去维护,一开始是想开C棵线段树,最后理智还是战胜了冲动。去查题解,才发现线段树还有动态开点这种操作。

我们正常的线段树某个根节点rt的左儿子的编号是rt<<1,右儿子的编号是rt<<1|1,而动态开线段树的话我们是一边存储一边给左右儿子编号,用root[c]来记录信仰宗教为c的城市的线段树的根节点,每次查询只查询相应点宗教那棵树上的值就行了,记得在修改宗教的时候要把原先那个宗教中的线段树的值变为0,还有就是w和c要一起更新,剩下的就是树链剖分和线段树的常规操作了,具体实现看代码吧。

#include <bits/stdc++.h>
using namespace std;
const int MX = 1e5+7;

struct edge{int v,nxt;}E[MX<<1];
int head[MX],tot;

int n,q,cnt,tmp;
int w[MX],c[MX];
int sz[MX],son[MX],fa[MX],dep[MX],top[MX],id[MX];
int root[MX];//记录不同宗教的线段树的根节点
struct tree{
    int sum,MAX;
    int ls,rs;
}T[MX*40];//数组记得开大点,因为要调用的空间很多,一开始开小一直RE

void init(){
    memset(head,-1,sizeof(head));
    tot = cnt = tmp = 0;
}

void add_edge(int u,int v){
    E[tot].v = v;E[tot].nxt = head[u];
    head[u] = tot++;
    E[tot].v = u;E[tot].nxt = head[v];
    head[v] = tot++;
}

void dfs1(int u){
    sz[u] = 1;son[u] = 0;
    for(int i = head[u];~i;i = E[i].nxt){
        int v = E[i].v;
        if(v == fa[u]) continue;
        fa[v] = u;dep[v] = dep[u] + 1;
        dfs1(v);
        sz[u] += sz[v];
        if(sz[v] > sz[son[u]]) son[u] = v;
    }
}

void dfs2(int u,int tp){
    id[u] = ++cnt; top[u] = tp;
    if(son[u]) dfs2(son[u],tp);
    for(int i = head[u];~i;i = E[i].nxt){
        int v = E[i].v;
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v,v);
    }
}

void push_up(int rt){
    T[rt].sum = T[T[rt].ls].sum + T[T[rt].rs].sum;
    T[rt].MAX = max(T[T[rt].ls].MAX,T[T[rt].rs].MAX);
}

void update(int p,int x,int l,int r,int &rt){
    //rt记得加&,因为是一边存储一边更新结点编号的
    if(!rt) rt = ++tmp;
    if(l == r){
        T[rt].sum = T[rt].MAX = x;
        return;
    }
    int m = (l + r) >> 1;
    if(p <= m) update(p,x,l,m,T[rt].ls);
    else update(p,x,m+1,r,T[rt].rs);
    push_up(rt);
}

int query_sum(int L,int R,int l,int r,int rt){
    if(L <= l && r <= R) return T[rt].sum;
    int m = (l + r) >> 1;
    int res = 0;
    if(L <= m) res += query_sum(L,R,l,m,T[rt].ls);
    if(R > m) res += query_sum(L,R,m+1,r,T[rt].rs);
    return res;
}

int query_max(int L,int R,int l,int r,int rt){
    if(L <= l && r <= R) return T[rt].MAX;
    int m = (l + r) >> 1;
    int res = -1;
    if(L <= m) res = max(res,query_max(L,R,l,m,T[rt].ls));
    if(R > m) res = max(res,query_max(L,R,m+1,r,T[rt].rs));
    return res;
}

int solve(int op,int u,int v,int col){
    int ans = 0;
    if(!op){
        while(top[u] != top[v]){
            if(dep[top[u]] < dep[top[v]]) swap(u,v);
            ans = max(ans,query_max(id[top[u]],id[u],1,n,root[col]));
            u = fa[top[u]];
        }
        if(dep[u] > dep[v]) swap(u,v);
        ans = max(ans,query_max(id[u],id[v],1,n,root[col]));
    } else{
        while(top[u] != top[v]){
            if(dep[top[u]] < dep[top[v]]) swap(u,v);
            ans += query_sum(id[top[u]],id[u],1,n,root[col]);
            u = fa[top[u]];
        }
        if(dep[u] > dep[v]) swap(u,v);
        ans += query_sum(id[u],id[v],1,n,root[col]);
    }
    return ans;
}

int main(){
    //freopen("in.txt","r",stdin);
    init();
    scanf("%d%d",&n,&q);
    for(int i = 1;i <= n;i++) scanf("%d%d",&w[i],&c[i]);
    for(int i = 1;i < n;i++){
        int u,v;scanf("%d%d",&u,&v);
        add_edge(u,v);
    }
    dfs1(1);dfs2(1,1);
    for(int i = 1;i <= n;i++)
        update(id[i],w[i],1,n,root[c[i]]);//每次对c[i]这个颜色的树更新
    while(q--){
        char op[3];scanf("%s",op);
        if(op[0] == 'C'){
            if(op[1] == 'C'){
                int p,x;scanf("%d%d",&p,&x);
                update(id[p],0,1,n,root[c[p]]);//记得先删去原来宗教那棵树上的值
                c[p] = x;
                update(id[p],w[p],1,n,root[c[p]]);
            } else{
                int p,x;scanf("%d%d",&p,&x);
                update(id[p],x,1,n,root[c[p]]);
                w[p] = x;
            }
        } else{
            if(op[1] == 'M'){
                int l,r;scanf("%d%d",&l,&r);
                //cout<<id[l]<<" "<<id[r]<<endl;
                printf("%d\n",solve(0,l,r,c[l]));
            } else{
                int l,r;scanf("%d%d",&l,&r);
                //cout<<id[l]<<" "<<id[r]<<endl;
                printf("%d\n",solve(1,l,r,c[l]));
            }
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值