POJ

题目传送门:POJ - 2763 Housewife Wind 

题目大意:

存在一个树形图,一个人一开始在s点,树上每条边都有时间花费,现在存在两种操作

1、  0 u   :查询这个人从他当前的点到点u的时间花费

 2、 1 i w :将第i条路径的时间花费更改为w

分析:

树链剖分,维护边权,用每条边深度较大的节点记录该边的边权,将边权改为点权,之后

即可用线段树单点更新,区间查询维护。

在更新节点权值时,对深度较大的节点进行更新,因此用结构体数组保存边信息后,将深度

较大的点记录在e[i].u中,方便更新操作。

注:在查询x->y路径和时,当x和y在同一条重链时,x点记录的权值为x->fa[x]的值,所以需要

用到的是son[x]->y所记录的值。并且在查询x->y路径时,当最后x和y值相同,则直接返回答案

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
#define ls l,m,rt<<1
#define rs m+1,r,rt<<1|1
const int MAX=100009;
int head[MAX],cnt=0;
int fa[MAX],deep[MAX],son[MAX],size[MAX],top[MAX],id[MAX],rk[MAX],tot;
int n,q,s,u,v,w,x,y,op;
int sum[MAX<<2],a[MAX];
struct Edge{
    int next,to;
}edge[MAX*2];
inline void add(int u,int v)
{
    edge[cnt].to=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}
struct E{
    int u,v,w;
}e[MAX];
void dfs1(int u,int f,int d)
{
    deep[u]=d;
    fa[u]=f;
    size[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v!=fa[u])
        {
            dfs1(v,u,d+1);
            size[u]+=size[v];
            if(son[u]==-1||size[v]>size[son[u]])
                son[u]=v;
        }
    }
} 
void dfstop(int u,int t)
{
    top[u]=t;
    id[u]=tot++;
    rk[id[u]]=u;
    if(son[u]==-1)return;
    dfstop(son[u],t);
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v!=fa[u]&&v!=son[u])    
            dfstop(v,v);
    }
}
void PushUp(int rt)//维护区间和 
{
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void Build(int l,int r,int rt)
{
    if(l==r)
    {
        sum[rt]=a[rk[l]];
        return;
    }
    int m=l+r>>1;
    Build(ls);Build(rs);
    PushUp(rt);
}
void Update(int pos,int val,int l,int r,int rt)//单点更新 
{
    if(l==r)
    {
        sum[rt]=val;
        return;
    }
    int m=l+r>>1;
    if(pos<=m)Update(pos,val,ls);
    else Update(pos,val,rs);
    PushUp(rt);
}
ll Query(int L,int R,int l,int r,int rt)//区间查询 
{
    if(L<=l&&r<=R)
        return sum[rt];
    int m=l+r>>1;
    ll ans=0;
    if(L<=m)ans+=Query(L,R,ls);
    if(R>m)ans+=Query(L,R,rs);
    return ans;
}
ll ssum(int x,int y)        //查询x->y路径和 
{
    ll ret=0;
    while(top[x]!=top[y])
    {
        if(deep[top[x]]<deep[top[y]])
            swap(x,y);
        ret+=Query(id[top[x]],id[x],1,tot,1);
        x=fa[top[x]];
    }
    if(x==y)return ret;        //!!!! 
    if(deep[x]>deep[y])swap(x,y);
    ret+=Query(id[son[x]],id[y],1,tot,1);//当x和y在同一条重链时,最后x->y的值记录在son[x]->y这个区间中 
    return ret;
}
void init()
{
    memset(head,-1,sizeof(head));cnt=0;
    memset(son,-1,sizeof(son));tot=1;
    memset(deep,0,sizeof(deep));
    memset(size,0,sizeof(size));
}
int main()
{
    scanf("%d%d%d",&n,&q,&s);
    init();
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
        add(e[i].u,e[i].v),add(e[i].v,e[i].u);
    }
    dfs1(1,-1,0);dfstop(1,1);
    for(int i=1;i<n;i++)
    {
        if(deep[e[i].u]<deep[e[i].v])//将边上深度较大的点记录在e[i].u中 
            swap(e[i].u,e[i].v);
        a[e[i].u]=e[i].w;    //将边的权值保存在深度较大的点上 
    }
    Build(1,tot,1);
    while(q--)
    {
        scanf("%d",&op);
        if(op)
        {
            scanf("%d%d",&x,&y);
            Update(id[e[x].u],y,1,tot,1);
        }
        else
        {
            scanf("%d",&x);
            printf("%lld
",ssum(s,x));
            s=x;    //更新当前位置 
        }
    }
    return 0;
}

总结:维护边权时,用边上深度较深的点记录边的权值,将边权改为点权操作,为了方便操作,

通常将边上深度较大的点放在记录边数组的统一位置,如(u,v)u记录的都是深度较大的点。其他的

就大同小异了。

该题为树链剖分--维护边权和

树链剖分--维护边权最大值:[SPOJ - QTREE]  Query on a tree    题解

原文地址:https://www.cnblogs.com/LjwCarrot/p/10828306.html