SPOJ - QTREE Query on a tree(树链剖分)

点我看题(给的vj上的链接…

题意:给出一个无环无向有权连通图,然后两个操作,   第一个操作:CHANGE i ti  ,把第i条边的权值改为ti,第二个操作:QUERY a b ,求结点a到结点b路径上经过的边的最大权值。

分析:第一次写树链剖分,觉得有点难= =,直接看代码注释吧(后期会把详细的分析补上

参考代码:

/*树链剖分入门题*/
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>

using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define lson rt<<1
#define rson rt<<1|1
const int maxn = 1e4+10;
int n;
//edge[i].to表示有结点u发出的一条边的另一个结点
//edge[edge[i].next].to
struct Edge{
    int to;
    int next;
};
Edge edge[maxn<<1];
int head[maxn],tot;//head[u]中的u表示邻接表的每个起点,head[u]表示以结点u开始指向的第一个结点,tot记录当前的边数
int size[maxn];//num[v]表示以v为根结点的子树的节点数
int top[maxn];//top[v]表示v所在的重链的顶端结点
int fa[maxn];//当前结点的父亲结点
int deep[maxn];//当前结点的深度
int pos[maxn];//pos[v]表示v与其父亲结点的连线在线段树中的位置,也就是线段树维护的第多少条链
int ffpos[maxn];//与pos数组相反????
int son[maxn];//重儿子
int posi;//????????
int e[maxn][3];//e[i][0]和e[i][1]表示边的两个端点编号,e[i][2]表示边的权值

//初始化
void init()
{
    tot = 0;//建图的时候初始边数为0
    mem(head,-1);//初始时每个结点都是孤立的
    posi = 0;
    mem(son,-1);//初始时没儿子
}

//邻接表构图
void AddEdge( int u, int v)
{
    edge[tot].to = v;//被指向的结点
    edge[tot].next = head[u];//下一个被指向的结点的下标,-1表示没了
    head[u] = tot++;//"中转编号"
}

//第一次dfs求重边,记录下fa[],deep[],size[],son[]
void dfs1( int u, int pre, int dep)
{
    deep[u] = dep;//当前根结点的深度为参数dep
    fa[u] = pre;//当前结点的父亲结点为参数pre
    size[u] = 1;//当前结点的子树初始大小为1,也就代表此时只有自己一个结点
    for( int i = head[u]; ~i; i = edge[i].next)
    {
        int v = edge[i].to;
        if( v != pre)//结点v不是u的父节点
        {
            dfs1(v,u,dep+1);//继续往下搜索,找子树的重边
            size[u] += size[v];//当前结点的大小为所有子树大小之和加上自己的一个1
            if( son[u] == -1 || size[v] > size[son[u]])//如果u当前没有子树或者u的当前儿子结点的大小比其重儿子的大小要大,那么改变其重儿子
                son[u] = v;
        }
    }
}

//第二次dfs连重边成重链,记录下top[],pos[]
void dfs2( int u, int tp)
{
    top[u] = tp;//重链的顶端结点为传进来的参数tp
    pos[u] = posi++;//
    ffpos[pos[u]] = u;//第多少条链的重链顶端结点是多少
    if( son[u] == -1)
        return;
    dfs2(son[u],tp);//如果u不是叶子结点,说明还可以往下走,他的顶端结点还是tp
    for( int i = head[u]; ~i; i = edge[i].next)//遍历结点u指向的其他结点
    {
        int v = edge[i].to;//被指向结点的编号
        if( v != son[u] && v != fa[u])//v结点要是没有和v结点形成重边同时也不是u的父亲结点,那么就要新开一条重链了
            dfs2(v,v);
    }
}

//线段树
struct SegTree{
    int l,r;//左右结点
    int maxx;//最大值
};
SegTree st[maxn<<2];

//建树
void Build( int l, int r, int rt)
{
    st[rt].l = l;
    st[rt].r = r;
    st[rt].maxx = 0;
    if( l == r)
        return;
    int mid = (l+r)>>1;
    Build(l,mid,lson);
    Build(mid+1,r,rson);
}

void PushUp( int rt)
{
    st[rt].maxx = max(st[lson].maxx,st[rson].maxx);
}

//更新线段树的第k个值为val
void Update( int rt, int k, int val)
{
    if( st[rt].l == k && st[rt].r == k)
    {
        st[rt].maxx = val;
        return;
    }
    int mid = (st[rt].l+st[rt].r)>>1;
    if( k <= mid)
        Update(lson,k,val);
    else
        Update(rson,k,val);
    PushUp(rt);
}

//查找线段树中[l,r]的最大值
int Query( int rt, int l, int r)
{
    if( st[rt].l == l && st[rt].r == r)
        return st[rt].maxx;
    int mid = (st[rt].l+st[rt].r)>>1;
    if( r <= mid)
        return Query(lson,l,r);
    else if( l > mid)
        return Query(rson,l,r);
    else
        return max(Query(lson,l,mid),Query(rson,mid+1,r));
}

//查询u->v边的最大值
int Find( int u, int v)
{
    int f1 = top[u];//找到u所在重链的顶端结点
    int f2 = top[v];//v所在重链的顶端结点
    int tmp = 0;
    while( f1 != f2)//两个结点不在同一条重链上
    {
        if( deep[f1] < deep[f2])//保持f1的深度大于f2的
        {
            swap(f1,f2);
            swap(u,v);
        }
        tmp = max(tmp,Query(1,pos[f1],pos[u]));//先找出前一个结点所在重链的顶端结点到另一个结点的最大边的权值
        u = fa[f1];//一直往树的上面走
        f1 = top[u];
    }
    if( u == v)//如果u一开始就等于v或者u通过上面的循环走到了v,直接返回找到的值
        return tmp;
    if( deep[u] > deep[v])//保证前一个的深度小于后一个
        swap(u,v);
    return max(tmp,Query(1,pos[son[u]],pos[v]));//找啊找
}

int main()
{
    int T;
    scanf("%d",&T);
    while( T--)
    {
        init();//初始化
        scanf("%d",&n);
        for( int i = 0; i < n-1; i++)
        {
            scanf("%d%d%d",&e[i][0],&e[i][1],&e[i][2]);
            AddEdge(e[i][0],e[i][1]);//建边
            AddEdge(e[i][1],e[i][0]);
        }

        dfs1(1,0,0);//第一次dfs
        dfs2(1,1);//第二次dfs

        //建线段树
        Build(0,posi-1,1);
        for( int i = 0; i < n-1; i++)
        {
            if( deep[e[i][0]] > deep[e[i][1]])//保持用深度大的那个去更新树
                swap(e[i][0],e[i][1]);
            Update(1,pos[e[i][1]],e[i][2]);//更新
        }

        char op[10];
        while( scanf("%s",op) == 1)
        {
            if( op[0] == 'D')
                break;
            int u,v;
            scanf("%d%d",&u,&v);
            if( op[0] == 'Q')
                printf("%d\n",Find(u,v));
            else if( op[0] == 'C')
                Update(1,pos[e[u-1][1]],v);
        }
    }

    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值