poj2763 Housewife Wind (树链剖分,边权,模板)

题意:

给一棵树,某人原来在 s 点,每条边有边权
q次操作
有两种操作:
(0,x)查询s走到x的路径和,操作结束之后s走到x
(1,x,y)将第x条路径长度改成y

输入:

第一行n,q,s
下面n-1行:
每行一条边u,v,w

下面q行:
每行一次操作

Sample Input

3 3 1
1 2 1
2 3 2
0 2
1 2 3
0 3

Sample Output

1
3

思路:

模板题

瞎逼总结一下:
基于边权的树链剖分:
1.不需要rk数组记录dfs序对应的原节点的点权(因为变成边权了嘛)

2.求出dfs序之后先建立一棵空树然后在插入点权(不是边权,下面有讲原因)
边权是先存储下来的,建立完空树插入,具体插入方法看代码。

3.因为边权比较难处理,所以把边权转化为点权,从树根开始,边权被附着在下面的点上(出边对应的点)
因为上面的点会有多个出边,下面的点只有一个入边,所以把边权附在下面的点上是唯一的。

4.dfs序区间查询的时候(包括其他可行的查询),因为边权附着在下面的点上了所以操作有一点点不一样,当u,v在同一条链上的时候(这里假设id[u]>id[v],即u是入点),如果u=v直接返回结果,否则区间查询id[son[u]]-id[v],其中id[x]为点x的dfs序,不用u用son[u]是因为边权被附着在下面的点上了,所以应该查询son[u]

5.不知道还有没有了,还是看代码

ps:
开始直接把所有的int改成longlong了,结果超时,改回int就过了
由此可见longlong的相关计算应该比int慢很多

code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxm=2e5+5;
const int M=maxm<<1;
int head[maxm],nt[M],to[M],cnt;
int fa[maxm];
int d[maxm];
int sz[maxm];
int son[maxm];
int top[maxm];
int id[maxm];
int n,m,s;
int q;
struct Node{
    int a,b,c;
}e[maxm];
struct Tree{
    int l,r,sum;
}a[maxm<<2];
void init(){
    memset(head,-1,sizeof head);
    cnt=1;
}
void add(int x,int y){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
void dfs1(int x){//sz,fa,d,son;
    sz[x]=1;
    son[x]=0;
    d[x]=d[fa[x]]+1;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(v==fa[x])continue;
        fa[v]=x;
        dfs1(v);
        sz[x]+=sz[v];
        if(sz[son[x]]<sz[v]){
            son[x]=v;
        }
    }
}
void dfs2(int x,int tp){//top,id;
    top[x]=tp;
    id[x]=++cnt;
    if(son[x]){
        dfs2(son[x],tp);
    }
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(v==fa[x]||v==son[x])continue;
        dfs2(v,v);
    }
}
void build(int l,int r,int node){//建空树
    a[node].l=l,a[node].r=r;
    a[node].sum=0;
    if(l==r)return ;
    int mid=(l+r)/2;
    build(l,mid,node*2);
    build(mid+1,r,node*2+1);
}
void pushup(int node){
    a[node].sum=a[node*2].sum+a[node*2+1].sum;
}
void update(int x,int val,int node){//单点更新
    int l=a[node].l,r=a[node].r;
    if(l==r){
        a[node].sum=val;
        return ;
    }
    int mid=(l+r)/2;
    if(x<=mid){
        update(x,val,node*2);
    }else{
        update(x,val,node*2+1);
    }
    pushup(node);
}
int ask(int st,int ed,int node){//区间求和
    int l=a[node].l,r=a[node].r;
    if(st<=l&&ed>=r){
        return a[node].sum;
    }
    int mid=(l+r)/2;
    int ans=0;
    if(st<=mid){
        ans+=ask(st,ed,node*2);
    }
    if(ed>=mid+1){
        ans+=ask(st,ed,node*2+1);
    }
    return ans;
}
int sum(int x,int y){
    int ans=0;
    while(top[x]!=top[y]){
        if(d[top[x]]<d[top[y]]){
            swap(x,y);
        }
        ans+=ask(id[top[x]],id[x],1);
        x=fa[top[x]];
    }
    if(id[x]>id[y]){
        swap(x,y);
    }
    if(x==y)return ans;//这里return的原因看下面一行的注释
    ans+=ask(id[son[x]],id[y],1);//因为边权被改到下面的点上了,所以查询的是id[son[x]]而不是id[x]
    return ans;
}
void change(int i,int val){
    int aa=e[i].a;
    int bb=e[i].b;
    if(d[aa]<d[bb]){
        swap(aa,bb);
    }
    update(id[aa],val,1);
}
signed main(){
    init();
    scanf("%d%d%d",&n,&q,&s);
    for(int i=1;i<n;i++){
        scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].c);
        add(e[i].a,e[i].b);
        add(e[i].b,e[i].a);
    }
    fa[1]=0;
    dfs1(1);
    cnt=0;
    dfs2(1,1);
    build(1,n,1);
    for(int i=1;i<n;i++){
        int aa=e[i].a;
        int bb=e[i].b;
        int cc=e[i].c;
        if(d[aa]<d[bb]){//把边权安排在下面的点上(如果安排在上面的点上会出现一点多权的情况)
            swap(aa,bb);
        }
        update(id[aa],cc,1);
    }
    while(q--){
        int d,x,y;
        scanf("%d",&d);
        if(d==0){//求s到x的路径长度
            scanf("%d",&x);
            printf("%d\n",sum(s,x));
            s=x;
        }else{//修改边权x为y
            scanf("%d%d",&x,&y);
            change(x,y);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值