POJ 2763

题意:给定一棵含n个结点的树,共有q次操作,分为两种

0 c :求从位置s到c的距离,然后s变成c

1 a b:把第a条边的权值变为b

n<10w,q<10w

题解:求树中两点距离显然是LCA,关于对原树中边权的修改,假设改了(a,b)使得其边增大了c,那么如果a是b的父亲,那么以b为子树的所有结点到根的距离都将增加c。求解LCA可以得到dfs序列,记录下每个结点初始访问时间以及最后访问时间,那么如果以该节点为子树的结点距根节点距离有变,只需对这个区间内每个值加上c即可,线段树可以做,但太麻烦了,实际上可以用树状数组直接在这区间头加上c,区间尾后面减去c,然后求值时受影响的就只有这个区间了。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 400005; // 1<<20;
int prt[N],pnt[N], next[N], head[N],cost[N]; // 邻接表
bool visited[N]; // 初始为0,从根遍历
int e,id;
int dep[N], E[N], R[N],ED[N],fa[N],dist[N]; // dep:dfs遍历节点深度, E:dfs序列, R:第一次被遍历的下标
void DFS(int u, int d);
int d[40], st[N][40];
int ar[N]; // index: 1 ~ N
int lowb(int t)
{
    return t&(-t) ;
}
void add(int i, int v)
{
    for ( ; i < N; ar[i] += v, i += lowb(i));
}
int sum(int i)
{
    int s = 0;
    for ( ; i > 0; s += ar[i], i -= lowb(i));
    return s;
}
int Query(int x, int y)
{
    int k;
    k = int( log(double(y-x+1))/log(2.0) );
    return dep[ st[x][k] ] > dep[ st[y-d[k]+1][k] ] ? st[y-d[k]+1][k] : st[x][k];
}
int LCA(int x,int y)
{
    if( x > y )
        swap(x,y);
    return E[Query(x, y)];
}
void DFS(int u, int d,int dis)
{
    visited[u] = 1;
    R[u] = id;
    E[id] = u;
    dist[u]=dis;
    dep[id++] = d;
    for( int i=head[u]; i != -1; i=next[i] )
        if( visited[ pnt[i] ] == 0 )
        {
            fa[pnt[i]]=u;
            DFS(pnt[i], d+1,cost[i]+dis);
            E[id] = u;
            dep[id++] = d;
        }
    ED[u]=id;
}
void InitRMQ()
{
    int i, j;
    for( d[0]=1, i=1; i < 20; ++i ) d[i] = 2*d[i-1];
    for( i=0; i < id; ++i ) st[i][0] = i;
    int k = int( log(double(N))/log(2.0) ) + 1;
    for( j=1; j < k; ++j )
        for( i=0; i < id; ++i )
            if( i+d[j-1]-1 < id )
                st[i][j]=dep[st[i][j-1]]>dep[ st[i+d[j-1]][j-1]]?st[i+d[j-1]][j-1]:st[i][j-1];
            else break;
}
void addedge(int a,int b,int c)
{
    prt[e]=a;pnt[e]=b;next[e]=head[a];cost[e]=c;head[a]=e++;
    prt[e]=b;pnt[e]=a;next[e]=head[b];cost[e]=c;head[b]=e++;
}
struct Edge
{
    int a,b,c;
    Edge(){}
    Edge(int _a,int _b,int _c)
    {
        a=_a,b=_b,c=_c;
    }
}EE[N];
int main()
{
    int n,q,s,a,b,c,op,x,y,z;
    while(scanf("%d%d%d",&n,&q,&s)!=EOF)
    {
        memset(head,-1,sizeof(head));
        memset(ar,0,sizeof(ar));
        memset(visited,0,sizeof(visited));
        id=e=0;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            EE[i]=Edge(a,b,c);
            addedge(a,b,c);
        }
        DFS(s,0,0);
        fa[s]=s;
        InitRMQ();
        for(int i=0;i<q;i++)
        {
            scanf("%d",&op);
            if(op==0)
            {
                scanf("%d",&b);
                x=R[s];y=R[b];z=R[c=LCA(x,y)];
                printf("%d\n",sum(x+1)+sum(y+1)-2*sum(z+1)+dist[s]+dist[b]-2*dist[c]);
                s=b;
            }
            else
            {
                scanf("%d%d",&a,&c);
                x=EE[a].a;
                y=EE[a].b;
                int tp=c-EE[a].c;
                EE[a].c=c;
                if(fa[x]==y)
                    swap(x,y);
                add(R[y]+1,tp);
                add(ED[y]+1,-tp);
            }
        }
    }
    return 0;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值