poj3728 倍增法lca 好题!

lca的好题!网上用st表和离线解的比较多,用树上倍增也是可以做的

不知道错在哪里,等刷完了这个专题再回来看

题解链接https://blog.csdn.net/Sd_Invol/article/details/9572423

/*                    
给一颗点权树,求出一个点对(x,y)之间的max{A,B,C}
A:x到lca路径上的最大差值
B:lca到y路径上的最大差值
C:x到y路径上的最大差值
需要维护的值,x结点到的祖先,x结点到祖先路径上的最大值,最小值,x结点到路径上的最大收益,最小收益(可以是负数)
对于每个询问x->y:A:x到祖先的最大收益
                 B:y到祖先的最小收益的负值
                 C:x到lca的最大值减去lca到y的最小值
*/
#include<iostream>    
#include<cstring>    
#include<cstdio>    
#include<cmath>    
#include<algorithm>
using namespace std;
#define maxn 50005
struct Edge{
    int x,next;
}e[maxn<<1];
int head[maxn],tot,n,q;
inline void addedge(int u,int v){
    e[tot].x=v;
    e[tot].next=head[u];
    head[u]=tot++;
}            
int d[maxn],f[maxn][16],a[maxn];//深度,祖先,点权
int fm[maxn][16],fn[maxn][16],sm[maxn][16],sn[maxn][16];//最大值,最小值,最大收益,最小收益
void init(){
    tot=0;    
    memset(head,-1,sizeof head);
}            
void dfs(int x,int fa,int dep){
    d[x]=dep,f[x][0]=fa;
    for(int i=head[x];i!=-1;i=e[i].next)
        if(fa!=e[i].x) dfs(e[i].x,x,dep+1);
}            
int query(int x,int y){
    int i,xx=0,yy=0,X=a[x],Y=a[y];//x侧最大收益,y侧最小收益,x侧最小值,y侧最大值
    i=15;    
    while(d[x]!=d[y]){//拉倒同一高度
        if(abs(d[x]-d[y]) >= 1<<i)
            if(d[y]<d[x]) 
                xx=max(max(xx,sm[x][i]),fm[x][i]-X),X=min(X,fn[x][i]),x=f[x][i];
            else 
                yy=min(min(yy,sn[y][i]),fn[y][i]-Y),Y=max(Y,fm[y][i]),y=f[y][i];
        --i;
    }        
    if(x==y) return max(max(xx,-yy),Y-X);
    i=15;    
    while(i>=0){
        if(f[x][i] && f[y][i] && f[x][i]!=f[y][i]){
            xx=max(max(xx,sm[x][i]),fm[x][i]-X),X=min(X,fn[x][i]),x=f[x][i];
            yy=min(min(yy,sn[y][i]),fn[y][i]-Y),Y=max(Y,fm[y][i]),y=f[y][i];
        }    
        --i;
    }        
    i=0;//这里还要跳一次
    xx=max(max(xx,sm[x][i]),fm[x][i]-X),X=min(X,fn[x][i]),x=f[x][i];
    yy=min(min(yy,sn[y][i]),fn[y][i]-Y),Y=max(Y,fm[y][i]),y=f[y][i];
    return max(max(xx,-yy),Y-X);
}            
void work(){
    int x,y;
    init();    
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<n;i++){
        scanf("%d%d",&x,&y);
        addedge(x,y);addedge(y,x);
    }        
    dfs(1,0,0);
    fm[1][0]=fn[1][0]=a[1];
    sm[1][0]=-1<<30;sn[1][0]=1<<30;
    for(int i=2;i<=n;i++){//先打初始状态
        fm[i][0]=max(a[i],a[f[i][0]]);//和父亲比较
        fn[i][0]=min(a[i],a[f[i][0]]);
        sm[i][0]=max(0,a[f[i][0]]-a[i]);//要么是0,要么赚了钱
        sn[i][0]=min(0,a[f[i][0]]-a[i]);//要么是0,要么是亏了钱
    }        
    for(int j=1;(1<<j)<n;j++)//再打剩下状态
        for(int i=1;i<=n;i++){
            int tmp=f[i][j-1];//中间态
            f[i][j]=f[tmp][j-1];
            fm[i][j]=max(fm[i][j-1],fm[tmp][j-1]);
            fn[i][j]=min(fm[i][j-1],fm[tmp][j-1]);
            sm[i][j]=max(max(sm[i][j-1],sm[tmp][j-1]),fm[tmp][j-1]-fn[i][j-1]);//最大收益要么是两段间的最大收益,要么是祖先段的最大收益减去子孙段的最小收益
            sn[i][j]=min(min(sn[i][j-1],sn[tmp][j-1]),fn[tmp][j-1]-fm[i][j-1]);//最小收益相反
        }    
    scanf("%d",&q);
    while(q--){
        scanf("%d%d",&x,&y);
        printf("%d\n",query(x,y));
    }
}
int main(){
    work();
    return 0;
}

 

posted on 2018-11-27 19:58  zsben  阅读(211)  评论(0编辑  收藏  举报

导航