树链剖分线段更新模板fzu2082过路费

Description

有n座城市,由n-1条路相连通,使得任意两座城市之间可达。每条路有过路费,要交过路费才能通过。每条路的过路费经常会更新,现问你,当前情况下,从城市a到城市b最少要花多少过路费。

Input

有多组样例,每组样例第一行输入两个正整数n,m(2 <= n<=50000,1<=m <= 50000),接下来n-1行,每行3个正整数a b c,(1 <= a,b <= n , a != b , 1 <= c <= 1000000000).数据保证给的路使得任意两座城市互相可达。接下来输入m行,表示m个操作,操作有两种:一. 0 a b,表示更新第a条路的过路费为b,1 <= a <= n-1 ; 二. 1 a b , 表示询问a到b最少要花多少过路费。

Output

对于每个询问,输出一行,表示最少要花的过路费。

Sample Input

2 31 2 11 1 20 1 21 2 1

Sample Output

12


#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <iostream>
#include <algorithm>
#define lson l,mid,o<<1
#define rson mid+1,r,o<<1|1
using namespace std;
const int maxn = 500005;
int son[maxn]; //表示该点重链的子节点
int num[maxn]; //表示以该点为根的子树的节点数量
int dep[maxn]; //表示该节点的深度
int fa[maxn];  // 表示该节点的父亲节点
int vis[maxn]; // 表示该节点在线段树中的编号
int top[maxn]; // 表示这条重链的顶端节点
int now;
int edge[maxn][3]; //保存所有的边
int head[maxn],cnt;
int n,m;
struct Edge
{
    int v,next;
}e[150005];
void add(int a,int b)
{
    e[cnt].v = b;
    e[cnt].next = head[a];
    head[a] = cnt++;
}
void init()
{
    memset(head,-1,sizeof(head));
    cnt = now = 0;
    memset(son,-1,sizeof(son));
}
void dfs1(int u,int v,int step) //第一遍dfs求出son,fa,num,dep
{
    dep[v] = step;
    num[v] = 1;
    fa[v] = u;
    int i;
    for(i = head[v]; i != -1; i = e[i].next)
    {
        int end = e[i].v;
        if(end == u)
            continue;
        dfs1(v,end,step+1);
        if(son[v] == -1 || num[son[v]] < num[end])   // 取子节点数量最多的点
        {
            son[v] = end;
        }
        num[v] += num[end];            
    }
}
void dfs2(int u,int v) // 第二遍dfs求出top和vis
{
    top[v] = u;
    vis[v] = now++;       //编号为1的节点不表示边,每条边取深度比较深的那个点为线段树中的一个节点
    if(son[v] != -1)      //将重链在线段树里连续编号
    {
        dfs2(u,son[v]);
    }
    else
        return;
    int i;
    for(i = head[v]; i != -1; i = e[i].next)
    {
        int end = e[i].v;
        if(end == fa[v] || end == son[v])  //等于父亲节点或者等于重链的子节点跳过,这里的u不是父亲节点,是重链的顶端
            continue;
        dfs2(end,end);   // 所有轻链重新为其节点的重链编号
    }
}
int A,B;
int sum[maxn << 2];
void update(int l,int r,int o)
{
    if(l == r)
    {
        sum[o] = B;
        return;
    }
    int mid = (l + r) >> 1;
    if(mid >= A) update(lson);
    else update(rson);
    sum[o] = sum[o<<1] + sum[o << 1 | 1];
}
int qurry(int l,int r,int o)
{
    if(A <= l && r <= B)
    {
        return sum[o];
    }
    int ans = 0;
    int mid = (l + r) >> 1;
    if(mid >= A) ans += qurry(lson);
    if(mid < B) ans += qurry(rson);
    return ans;
}
//更新的时候记得用vis取得线段树下标,不要用原来树中的编号。
//当两个点的不在同一条重链上的时候重链的顶端节点要取,当在同一条重链上的时候重链的顶端节点不能取,要
//取顶端节点的son节点,因为顶端节点取了相当于多取了一条边
void solve(int a,int b,int c)
{
    int q,w;
    if(c == 0)
    {
        A = vis[edge[a][0]];
        B = b;
        update(1,n-1,1);
    }
    else
    {
        q = top[a],w = top[b];
        int ans = 0;
        while(q != w)
        {
            if(dep[q] < dep[w])
            {
                swap(q,w);
                swap(a,b);
            }
            A = vis[q],B = vis[a];
            a = fa[q];
            q = top[a];
            ans += qurry(1,n-1,1);
        }
        if(a != b)
        {
            if(dep[a] > dep[b])
                swap(a,b);
            A = vis[son[a]], B = vis[b];
            ans += qurry(1,n-1,1);
        }
        printf("%d\n",ans);
    }
}
int main()
{
    while(scanf("%d %d",&n,&m) != EOF)
    {
        init();
        int i,j;
        for(i = 1; i < n; i++)
        {
            scanf("%d %d %d",&edge[i][0],&edge[i][1],&edge[i][2]);
            add(edge[i][0],edge[i][1]);
            add(edge[i][1],edge[i][0]);
        }
        dfs1(1,1,1);
        dfs2(1,1);
        for(i = 1; i < n; i++)
        {
            if(dep[edge[i][0]] < dep[edge[i][1]])
                swap(edge[i][0],edge[i][1]);
            A = vis[edge[i][0]],B = edge[i][2];
            update(1,n-1,1);
        }
        while(m--)
        {
            int a1,b1,c1;
            scanf("%d %d %d",&a1,&b1,&c1);
            solve(b1,c1,a1);
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值