hdu 5044 Tree(树链剖分)

本文详细介绍了一种基于树链剖分的算法实现,该算法能够高效地处理在一个简单图上进行多次点权和边权更新的问题。通过巧妙的数据结构和更新策略,避免了使用线段树带来的效率问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给一个简单图,在图上进行n此操作,最后按顺序输出点权和边权的值。T组数据,每组数据第一行两个数n,m分别代表树的点数,操作的次数,接下来n-1行每行两个数表示一条边。接下来m行表示m次操作,每次操作有一一个字符串add1,或add2,接下来是三个数u,v,c。add1表示在修改点权u->v的路径上的点加c,add2表示修改边权u->v的路径上的边权加c。

解题思路:知道题虽然是比较经典的树链剖分,但是并不是那么简单,首先它既要修改边权,又要修改点权这就非常考验对树链剖分的熟练度了,然后就是坑人的,不能用线段树去维护剖分出来的链,这么做会TLE。这里也是学到了一种非常巧妙的算法。用一个sum数组来进行标记进行的区间跟新,感觉跟扫描线的思想有点相似,最后操作完之后用另一个数组从头到尾扫一遍就好了。还是直接看代码,代码还是比较直观的。

这里有比较详细的树链剖分讲解:传送门

#include <set>
#include <map>
#include <stack>
#include <cmath>
#include <queue>
#include <cstdio>
#include <string>
#include <vector>
#include <iomanip>
#include <bitset>
#include <cstring>
#include <iostream>
#include <iosfwd>
#include <deque>
#include <algorithm>
#define Memset(a,val) memset(a,val,sizeof(a))
#define PI acos(-1.0)
#define PB push_back
#define MP make_pair
#define rt(n)       (i == n ? '\n' : ' ')
#define hi         printf("Hi----------\n")
#define IN freopen("input.txt","r",stdin);
#define OUT freopen("output.txt","w",stdout);
#define debug(x) cout<<"Debug : ---"<<x<<"---"<<endl;
#define debug2(x,y) cout<<"Debug : ---"<<x<<" , "<<y<<"---"<<endl;

#pragma comment(linker, "/STACK:1024000000,1024000000")
///有时数据范围较大,代码中两个DFS可能需要手动扩栈

using namespace std;
typedef pair<int,int> PII;
typedef long long ll;
const int maxn=100000+5;
const int mod=1000000007;
const int INF=0x3f3f3f3f;
const double eps=1e-8;

const int Vmax = 1*1e5 + 5;//点的数量
const int Emax =2*1e5+5;//边的数量 小于Vmax的两倍

namespace poufen{
    int siz[Vmax],son[Vmax],fa[Vmax],dep[Vmax],top[Vmax],w[Vmax];
    int nodenum;
    int sum1[Vmax],sum2[Vmax];
    ///邻接表存图
    struct edge{
        int v,next;
    }e[Emax];
    int pre[Vmax],ecnt;
    inline void init(){
        memset(pre, -1, sizeof(pre));
        ecnt=0;
    }
    inline void add_(int u,int v){
        e[ecnt].v=v;
        e[ecnt].next=pre[u];
        pre[u]=ecnt++;
    }

    inline void update1(int u,int v,int c)///更新点权
    {
        sum1[u]+=c;
        sum1[v+1]-=c;
    }
    inline void update2(int u,int v,int c)///更新边权
    {
        sum2[u]+=c;
        sum2[v+1]-=c;
    }
    void dfs(int u){///dfs跑出siz、son、fa、dep的值
        siz[u]=1;son[u]=0;//下标从1开始,son[0]初始为0
        for(int i=pre[u];~i;i=e[i].next)
        {
            int v=e[i].v;
            if(fa[u]!=v)
            {
                fa[v]=u;
                dep[v]=dep[u]+1;//初始根节点dep!!
                dfs(v);
                siz[u]+=siz[v];
                if(siz[v]>siz[son[u]])son[u]=v;
            }
        }
    }
    void build_tree(int u,int tp){///递归建树,记录top、w的值
        top[u]=tp,w[u]=++nodenum;
        if(son[u])build_tree(son[u],tp);
        for(int i=pre[u];~i;i=e[i].next)
            if(e[i].v!=fa[u]&&e[i].v!=son[u])
                build_tree(e[i].v,e[i].v);
    }

    void upd1(int u,int v,int c)///更新点权
    {
        int f1=top[u],f2=top[v];
        while(f1!=f2)
        {
            if(dep[f1]<dep[f2])
                swap(f1,f2),swap(u,v);
            update1(w[f1],w[u],c);
            u=fa[f1];
            f1=top[u];
        }
        if(dep[u]>dep[v])
            swap(u,v);
        update1(w[u],w[v],c);
    }

    void upd2(int u,int v,int c)///更新边权
    {
        int f1=top[u],f2=top[v];
        while(f1!=f2)
        {
            if(dep[f1]<dep[f2])
                swap(f1,f2),swap(u,v);
            update2(w[f1],w[u],c);
            u=fa[f1];
            f1=top[u];
        }
        if(u==v)
            return ;
        if(dep[u]>dep[v])
            swap(u,v);
        update2(w[son[u]],w[v],c);
        ///注意这里是son[u]因为最终根节点对应的边权不进行更新
    }

    int a[Vmax],b[Vmax];
    ll ans1[Vmax],ans2[Vmax];
    int val[Vmax];

}
using namespace poufen;

int main()
{
    int T,cas=1;
    scanf("%d",&T);
    while(T--)
    {
        int n,m;
        printf("Case #%d:\n",cas++);
        scanf("%d%d",&n,&m);
        memset(siz, 0, sizeof(siz));
        memset(sum1, 0, sizeof(sum1));
        memset(sum2,0,sizeof(sum2));
        init();
        int root=1;
        fa[root]=nodenum=dep[root]=0;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d",&a[i],&b[i]);
            add_(a[i],b[i]);
            add_(b[i],a[i]);
        }
        dfs(root);
        build_tree(root,root);
        char op[10];
        int u,v,c;
        for(int i=1;i<=m;i++)
        {
            scanf("%s%d%d%d",op,&u,&v,&c);
            if(op[3]=='1')
                upd1(u,v,c);
            else
                upd2(u,v,c);
        }
        for(int i=1;i<=n;i++)
        {
            ans1[i]=ans1[i-1]+sum1[i];
            ans2[i]=ans2[i-1]+sum2[i];
        }
        printf("%I64d",ans1[w[1]]);
        for(int i=2;i<=n;i++)
        {
            printf(" %I64d",ans1[w[i]]);
        }
        printf("\n");
        for(int i=1;i<n;i++)
        {
            int u=a[i],v=b[i];
            if(dep[u]>dep[v])   swap(u,v);
            printf("%I64d",ans2[w[v]]);
            if(i!=n-1)
                printf(" ");
        }
        printf("\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值