Gym101138J ————Valentina and the Gift Tree (树链剖分,区间最大子段和,线段树)

46 篇文章 1 订阅
11 篇文章 0 订阅

The Valentina's birthday is coming soon! Her parents love her so much that they are preparing some gifts to give her. Valentina is cute and smart and her parents are planning to set an interesting task for her.

They prepared a tree (a connected graph without cycles) with one gift in each vertex. Vertices are numbered 1 through n and the i-th of them contains a gift with value gi (a value describing Valentina's happiness if she gets a gift from this vertex). All gifts are wrapped so Valentina doesn't know their values.

Note that gi may be negative and it would mean that Valentina doesn't like the gift (do you remember when your parents gave you clothes or toys not adequate to your interests?).

Let's consider the following process:

  1. Valentina chooses two vertices a and b (not necessarily distinct).
  2. She unpacks all gifts on the path connecting a and b and thus she sees their values.
  3. She chooses some part of the path connecting a and b. The chosen part must be connected and can't be empty (hence, it must be a path).
  4. Valentina takes all gifts in the chosen part of the path and her total happiness is equal to the sum of the values of taken gifts.

Valentina is smart and for chosen a and b she will choose a part resulting in the biggest total happiness.

In order to maximize her chance to get a good bunch of gifts, parents allow her to ask q questions, each with two numbers a and b. For each Valentina's question parents will tell her the answer, i.e. the maximum total happiness for chosen a and b.

They noticed that answering Valentina's questions is an interesting problem. They want to know if you are smart enough to correctly answer all questions.

Input

The first line of the input contains one integer n (2 ≤ n ≤ 105) — the size of the tree.

Each of the next n - 1 lines contains two integers ui and vi (1 ≤ ui, vi ≤ n) — indices of two vertices connected with an edge. The given edges are guaranteed to describe a tree.

The next line contains n integers g1, g2, ... gn ( - 109 ≤ gi ≤ 109).

Then, the questions are given. One line contains an integer q (1 ≤ q ≤ 500 000) — the number of questions.

Finally, each of the last q lines contains two integers ai and bi (1 ≤ ai, bi ≤ n), describing one question.

Output

For each of the q questions find the maximum happiness of Valentina if she has to choose a non-empty part of the given path. For every question print the answer in a separate line.

Example
Input
6
1 2
1 3
1 4
4 5
4 6
-1 2 2 -2 5 8
6
2 5
2 3
1 5
5 3
1 4
5 6
Output
5
3
5
5
-1
11


简单来说,就是一棵树,每个节点有不同权值,然后有很多个查询,查询区间[u,v]上的最大子段和。

第一反应就是树链剖分,但是不知道怎么用线段树维护最大子段和。查了一下之后知道了。

线段树每个区间维护四个值,区间和,区间最大前缀和,区间最大后缀和,区间最大子段和。分别设为sum,suml,sumr,MAX.

那么更新的方程就是sum=l.sum+r.sum,suml=max(l.suml,l.sum+r.suml),sumr=max(l.sumr+r.sum,r.sumr),MAX=max(l.MAX,r.MAX,l.sumr+r.suml).

由此就能用线段树维护最大子段和了。

另外还有一个难点,如果用树链剖分维护区间最大值或者区间和,我们可以分段计算,也就是说轻链和重链可以分开。不过维护区间子段和不行。我们需要把轻链连接到最后一个重链的两端,所谓的连接方法和线段树的pushup更新类似。


#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
using namespace std;

const int MAXN = 100010;
const long long INF=0x3f3f3f3f3f3f3f3f;
struct Edge
{
    int to,next;
}edge[MAXN*2];
int head[MAXN],tot;
int top[MAXN];//top[v]表示v所在的重链的顶端节点
int fa[MAXN]; //父亲节点
int deep[MAXN];//深度
int num[MAXN];//num[v]表示以v为根的子树的节点数
int p[MAXN];//p[v]表示v与其父亲节点的连边在线段树中的位置
int fp[MAXN];//和p数组相反
int son[MAXN];//重儿子
int pos;

int n;

void init()
{
    tot = 0;
    memset(head,-1,sizeof(head));
    pos = 1;//序号其实是从1开始?
    memset(son,-1,sizeof(son));
}
void addedge(int u,int v)
{
    edge[tot].to = v;edge[tot].next = head[u];head[u] = tot++;
}
void dfs1(int u,int pre,int d) //第一遍dfs求出fa,deep,num,son
{
    deep[u] = d;
    fa[u] = pre;
    num[u] = 1;
    for(int i = head[u];i != -1; i = edge[i].next)
    {
        int v = edge[i].to;
        //因为路径是双向的,所以不能等于父及诶单
        if(v != pre)
        {
            //先递归地找到儿子节点的深度,父节点,子节点数目等信息
            dfs1(v,u,d+1);
            //更新u节点的儿子数目
            num[u] += num[v];
            if(son[u] == -1 || num[v] > num[son[u]])//求出重儿子
                son[u] = v;
        }
    }
}

//因为对于轻儿子来说,top[u]=u,对于重儿子来说,如果son[v]!=-1,那么top[v]=top[son[v]]
void getpos(int u,int sp) //第二遍dfs求出top和p
{
    top[u] = sp;
    //先找重儿子
    if(son[u] != -1)
    {
        //把边的位置标记一下
        p[u] = pos++;
        //fp相当于是p的反函数?
        fp[p[u]] = u;
        //更新重儿子
        getpos(son[u],sp);
    }
    //如果到了叶子节点
    else
    {
        //不再向下dfs
        p[u] = pos++;
        fp[p[u]] = u;
        return;
    }
    //更新其他的节点
    for(int i = head[u] ; i != -1; i = edge[i].next)
    {
        int v = edge[i].to;
        if(v != son[u] && v != fa[u])
            getpos(v,v);
    }
}

#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
struct xx{
    long long sum=0;
    long long MAX=-INF;
    long long suml=0,sumr=0;
    xx operator +  (xx const &a) const {
        xx NEW;
        NEW.sum=sum+a.sum;
        NEW.suml=max(suml,sum+a.suml);
        NEW.sumr=max(sumr+a.sum,a.sumr);
        NEW.MAX=max(max(MAX,a.MAX),sumr+a.suml);
        return NEW;
    }
};
xx node [MAXN<<2];
int val[MAXN<<2];

void pushup(int rt){
    node[rt].sum=node[rt<<1].sum+node[rt<<1|1].sum;
    node[rt].suml=max(node[rt<<1].suml,node[rt<<1].sum+node[rt<<1|1].suml);
    node[rt].sumr=max(node[rt<<1|1].sumr,node[rt<<1].sumr+node[rt<<1|1].sum);
    node[rt].MAX=max(max(node[rt<<1].MAX,node[rt<<1|1].MAX),node[rt<<1].sumr+node[rt<<1|1].suml);
}

void build(int l,int r,int rt){
    if(l==r){
        node[rt].sum=node[rt].MAX=node[rt].suml=node[rt].sumr=val[fp[l]];
        return;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
    pushup(rt);
}

xx query(int L,int R,int l,int r,int rt){
    if(l>=L&&r<=R){
        return node[rt];
    }
    int m=(l+r)>>1;
    if(m<L)
       return query(L,R,rson);
    if(R<=m)
        return query(L,R,lson);
    return (query(L,R,lson)+query(L,R,rson));
}

long long _find(int u,int v){
    int f1=top[u],f2=top[v];//先找到两个端点的重链顶端节点,如果是轻儿子,就是它本身
    xx lc,rc;
    while(f1!=f2){
        //从深度较深的开始查询
        if(deep[f1]>deep[f2]){
             lc=query(p[f1],p[u],1,n,1)+lc;
             u=fa[f1];f1=top[u];
        }
        else{
            rc=query(p[f2],p[v],1,n,1)+rc;
            v=fa[f2];f2=top[v];
        }
    }
    //如果f1=f2代表在同一条重链上m,如果u=v代表更新结束
    if(deep[u]>deep[v]){
        lc=query(p[v],p[u],1,n,1)+lc;
    }
    else{
        rc=query(p[u],p[v],1,n,1)+rc;
    }
      swap(lc.suml,lc.sumr);
    return (lc+rc).MAX;

}

int main(){
      //  freopen("in.txt","r",stdin);
        init();
        scanf("%d",&n);
        for(int i=0;i<n-1;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            addedge(u,v);
            addedge(v,u);
        }
        for(int i=1;i<=n;i++)
            scanf("%d",val+i);
        dfs1(1,0,0);
        getpos(1,1);
        build(1,n,1);
        int q;
        scanf("%d",&q);
        while(q--){
            int u,v;
            scanf("%d%d",&u,&v);
            printf("%lld\n",_find(u,v));
        }
    return 0;
}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值