Tree

Description

You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edges are numbered 1 through N − 1. Each edge is associated with a weight. Then you are to execute a series of instructions on the tree. The instructions can be one of the following forms:

Input

The input contains multiple test cases. The first line of input contains an integer t (t ≤ 20), the number of test cases. Then follow the test cases.

Each test case is preceded by an empty line. The first nonempty line of its contains N (N ≤ 10,000). The next N − 1 lines each contains three integers ab and c, describing an edge connecting nodes a and b with weight c. The edges are numbered in the order they appear in the input. Below them are the instructions, each sticking to the specification above. A lines with the word “DONE” ends the test case.

Output

For each “QUERY” instruction, output the result on a separate line.

Sample Input

1

3
1 2 1
2 3 2
QUERY 1 2
CHANGE 1 3
QUERY 1 2
DONE

Sample Output

1
3

Source

POJ Monthly--2007.06.03, Lei, Tao

Time Limit: 5000MS

 

Memory Limit: 131072K

Total Submissions: 11989

 

Accepted: 3088

[Submit]   [Go Back]   [Status]   [Discuss]

【题意】

给一棵树,每条边有一个权值。有三种操作:1.修改某条边权值 2.将uv两点之间路径上的边权值全部取相反数 3. 查询uv两点之间路径上的最大值。

【分析】

树建图后,除根节点外,每条边的权值转移给对应儿子结点,然后就变成了点权的树链剖分。

1.  树链剖分一下,顺便记一个数组 bev[i] 表示第i条边转移给了哪个点。

2. 线段树维护最大值最小值,当进行区间取反操作时,取反并交换即可。

【代码】

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
using namespace std;
typedef long long ll;
const int MAX=5e4+5;
const int INF=0x3f3f3f3f;

/*********** 图 ***********/
struct node{
    int t,next;
    ll w;
    int id;
}edge[MAX*2];
int head[MAX],cnt;
void init(int n)
{
    cnt=0; memset(head,-1,sizeof(head[0])*(n+1));
}
void addedge(int u,int v,int w,int id)
{
    edge[cnt]=node{v,head[u],w,id};
    head[u]=cnt++;
}

/************ 树链剖分 **************/
int dep[MAX],dad[MAX],siz[MAX],son[MAX],bev[MAX]; //bev[i]表示第i边对应的点
int top[MAX],rk[MAX],id[MAX];
void dfs1(int u,int pre)
{
    siz[u]=1; //u本身
    son[u]=-1;
    int maxsiz=0; //最大的儿子子树大小
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].t;
        if(v==pre)continue;
        dep[v]=dep[u]+1;
        dad[v]=u;
        bev[edge[i].id]=v; //边对应点
        dfs1(v,u); //注意dfs的位置
        siz[u]+=siz[v];
        if(maxsiz<siz[v])
        {
            maxsiz=siz[v];
            son[u]=v;
        }
    }
}
void dfs2(int u,int fat,int &tag) //结点u,重链顶端
{
    top[u]=fat;
    rk[u]=++tag; //时间戳
    id[tag]=u; //tag对应在树上的结点号
    if(son[u]==-1)return; //没有孩子
    dfs2(son[u],fat,tag); //优先重链dfs
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].t;
        if(v==dad[u]||v==son[u])continue;
        dfs2(v,v,tag);
    }
}
void cuttree(int root) //将root为根的树链剖分
{
    int tag=0;
    dep[root]=0;  //根的深度和父结点做一个初始化
    dad[root]=root;   //这是一个假设,根的父结点还是根
    dfs1(root,root);
    dfs2(root,root,tag);
}

/************* 线段树 ****************/
ll Max[MAX<<2],Min[MAX<<2],mark[MAX<<2];  //mark标记 区间是否取反
void build(int root,int l,int r) //初始线段树
{
    mark[root]=0;
    Max[root]=-INF;
    Min[root]=INF;
    if(l==r)return;//叶子节点
    int mid=(l+r)/2;
    build(root*2,l,mid);   //左子树的创建
    build(root*2+1,mid+1,r); //右子树
}
void nodeupdate(int root,int l,int r,ll num) //因题而异
{
    mark[root]^=1;
    swap(Max[root],Min[root]);
    Max[root]*=-1;
    Min[root]*=-1;
}
void pushdown(int root,int l,int r)//传递给两孩子
{
    if(mark[root]==0)return;
    int mid=(l+r)/2;
    nodeupdate(root*2,l,mid,mark[root]);
    nodeupdate(root*2+1,mid+1,r,mark[root]);
    mark[root]=0;
}
void pushup(int root,int l,int r)
{
    Max[root]=max(Max[root<<1],Max[root<<1|1]);
    Min[root]=min(Min[root<<1],Min[root<<1|1]);
}
void update(int left,int right,ll num, int root,int l,int r)//修改
{
    if(left<=l&&r<=right){
        Max[root]=Min[root]=num;
        return;
    }
    pushdown(root,l,r);
    int mid=(l+r)/2;
    if(left<=mid)
        update(left,right,num,root*2,l,mid);
    if(mid<right)
        update(left,right,num,root*2+1,mid+1,r);
    pushup(root,l,r);
}

void negative(int left,int right, int root,int l,int r)//区间[kl,kr]取反
{
    if(left<=l&&r<=right){
        nodeupdate(root,l,r,1);
        return;
    }
    pushdown(root,l,r);
    int mid=(l+r)/2;
    if(left<=mid)
        negative(left,right,root*2,l,mid);
    if(mid<right)
        negative(left,right,root*2+1,mid+1,r);
    pushup(root,l,r);
}

ll Qmax(int left,int right, int root,int l,int r)//区间最大值
{
    if(left<=l&&r<=right)
        return Max[root];
    pushdown(root,l,r);
    int mid=(l+r)/2;
    ll t1=-INF,t2=-INF; ///赋初值!!!
    if(left<=mid)
        t1=Qmax(left,right,root*2,l,mid);//区间在左子树
    if(right>mid)
        t2=Qmax(left,right,root*2+1,mid+1,r);//在右子树
    return max(t1,t2);
}

/************** 树链->线段树 *********/
void modify(int u,int v,int limit) //更新,negative
{
    int fu=top[u],fv=top[v];
    while(fu!=fv) //uv不在同一条链上
    {
        if(dep[fu]>=dep[fv]) //u的深度大
        {
            negative(rk[fu],rk[u],1,1,limit);
            u=dad[fu]; fu=top[u];
        }
        else
        {
            negative(rk[fv],rk[v],1,1,limit);
            v=dad[fv]; fv=top[v];
        }
    } //循环结束时,fu-fv在同一链
    if(dep[u]>dep[v])swap(u,v);
    if(u!=v)negative(rk[u]+1,rk[v],1,1,limit); //注意!边权题,lca不取
}
ll query(int u,int v, int limit) //查询u-v路径
{
    ll res=-INF;
    int fu=top[u],fv=top[v];
    while(fu!=fv) //uv不在同一条链上
    {
        if(dep[fu]>=dep[fv]) //u的深度大
        {
            res=max(res,Qmax(rk[fu],rk[u],1,1,limit));
            u=dad[fu]; fu=top[u];
        }
        else
        {
            res=max(res,Qmax(rk[fv],rk[v],1,1,limit));
            v=dad[fv]; fv=top[v];
        }
    } //循环结束时,fu-fv在同一链
    if(dep[u]>dep[v])swap(u,v);
    if(u!=v)
        res=max(res,Qmax(rk[u]+1,rk[v],1,1,limit)); ///注意+1,lca不取
    return res;
}

/************** solve **************/
int a[MAX];
bool solve()
{
    int n,u,v,w,k;
    scanf("%d",&n);
    init(n); //初始图
    for(int i=1;i<n;i++) //边权
    {
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w,i);
        addedge(v,u,w,i);
    }
    cuttree(1); //树链剖分
    build(1,1,n); //建立线段树
    for(int i=1;i<n;i++)a[bev[i]]=edge[(i-1)*2].w; //边权转化为点权
    //以下变为点权做法,但是lca结点不可取!!!这是与纯点权图的区别
    for(int i=2;i<=n;i++)update(rk[i],rk[i],a[i],1,1,n);//初态
    char op[12];
    while(scanf("%s",op),op[0]!='D')
    {
        if(op[0]=='C') //边u权值改为v
        {
            scanf("%d%d",&u,&v);
            update(rk[bev[u]],rk[bev[u]],v,1,1,n);//边对应的点要修改
        }
        else if(op[0]=='N') //u-v路径权值取相反数
        {
            scanf("%d%d",&u,&v);
            modify(u,v,n); //这里是点之间
        }
        else   //查询区间最大值
        {
            scanf("%d%d",&u,&v);
            int ans=query(u,v,n); //注意这里是查询点之间
            printf("%d\n",ans);
        }
    }
}
int main()
{
    int T;
    for(scanf("%d",&T);T--;)solve();
}