poj 3728 lca问题(tarjan+并查集)+dp思路 (树上买卖的最大收益)

题意:给出一棵带点权的树,给出Q个询问(a,b),问从a到b的最大盈利(即:先在最小值买入,再在最大值卖出)。要求只能买入一次卖出一次,且买入在卖出之前。如果不能收益正值,则输出0。

思路:分析参考了(http://blog.csdn.net/xingyeyongheng/article/details/20402603)

对于每一对查询(u,v),先求出点u,v的最近公共祖先f,然后求u->f->v的利润最大值maxval
对于这个maxval可能有三种情况:
1:maxval是u->f的maxval
2:maxval是f->v的maxval
3:maxval是f->v的最大w[i]减去u->f的最小w[i] 

分析到这很明显需要设置4个变量来求maxval:
up[u]表示u->f的最大maxval
down[u]表示f->u的最大maxval
maxw[u]表示u-f的最大w[i]
minw[u]表示u-f的最小w[i]
所以maxval=max(max(up[u],down[v]),maxw[v]-minw[u]);
现在问题就是如何快速的求出这四个变量,在这里我们可以对u,v的LCA(u,v)进行分类解决
对于LCA(u,v)是f的询问全部求出,然后再求LCA(u,v)是f的父亲的询问
这样当我们求LCA(u,v)是f的父亲的询问的时候就可以借用已经求出的LCA(u,v)是f的询问
的结果,这样就不用反复去求u->f的那四个变量值,u->father[f]也能快速求出
这个变化主要在寻找father[v]这个过程中进行,具体看代码

#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
using namespace std;
#define INF 0x3fffffff
#define clr(s,t) memset(s,t,sizeof(s));
#define N 50005
int f1[N],f2[N],f3[N],top1,top2,top3;
struct edge{
    int y,next,id;
}e1[N<<1],e2[N<<1],e3[N];
struct question{
    int x,y,res;
}q[N];
int n,m;
int up[N],down[N],minv[N],maxv[N];
int used[N],root[N];
void add1(int x,int y){
    e1[top1].y = y;
    e1[top1].next = f1[x];
    f1[x] = top1++;
}
void add2(int x,int y,int id){
    e2[top2].y = y;
    e2[top2].id = id;
    e2[top2].next = f2[x];
    f2[x] = top2++;
}
void add3(int x,int y,int id){
    e3[top3].y = y;
    e3[top3].id = id;
    e3[top3].next = f3[x];
    f3[x] = top3++;
}
int find(int x){
    int fa;
    if(x == root[x])
        return x;
    fa = root[x];
    root[x] = find(root[x]);
    up[x] = max(max(up[x],up[fa]),maxv[fa]-minv[x]);
    down[x] = max(max(down[x],down[fa]),maxv[x]-minv[fa]);
    minv[x] = min(minv[x],minv[fa]);
    maxv[x] = max(maxv[x],maxv[fa]);
    return root[x];
}
void solve(int x){
    int i,j,a,b;
    used[x] = 1;
    root[x] = x;
    for(i = f2[x];i!=-1;i=e2[i].next){
        if(!used[e2[i].y])
            continue;
        j = find(e2[i].y);
        add3(j,e2[i].y,e2[i].id);//记录一下这个查询的LCA,此时已经能够求出来了就是j
    }
    for(i = f1[x];i!=-1;i=e1[i].next){
        if(used[e1[i].y])
            continue;
        solve(e1[i].y);
        root[e1[i].y] = x;
    }
    for(i = f3[x];i!=-1;i=e3[i].next){//求查询的LCA是x的情形
        j = e3[i].id;
        a = q[j].x;
        b = q[j].y;
        find(a);
        find(b);
        q[j].res = max(max(up[a],down[b]),maxv[b]-minv[a]);
    }
}
int main(){
    int i,j,a,b;
    scanf("%d",&n);
    for(i = 1;i<=n;i++)
        f1[i] = f2[i] = f3[i] = -1;
    top1 = top2 = top3 = 0;
    clr(used, 0);
    for(i = 1;i<=n;i++){
        scanf("%d",&j);
        minv[i] = maxv[i] = j;
        up[i] = down[i] = 0;
    }
    for(i = 1;i<n;i++){
        scanf("%d %d",&a,&b);
        add1(a,b);
        add1(b,a);
    }
    scanf("%d",&m);
    for(i = 1;i<=m;i++){
        scanf("%d %d",&q[i].x,&q[i].y);
        add2(q[i].x,q[i].y,i);
        add2(q[i].y,q[i].x,i);
    }
    solve(1);
    for(i = 1;i<=m;i++)
        printf("%d\n",q[i].res);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值