Educational Codeforces Round 46 G. Two-Paths 树形DP 树上两点路径长度变形。

点我看题

题意 : 给出一颗树,每条边有过路费,每个点有价值
定义一种名叫tow-path的路径 这种路径上 每条边可以经过最多两次
路径的权值为:
这里写图片描述
既路径上所有经过的点的权值和减去过路费之和;
现在有q次询问 每次询问给出两个点u,v 。
对于每次询问 要求输出u到v的tow-path 最大权值。

解题思路:
首先,看见这题应该要联想到 求树上两点路径长度 这一经典问题。
考虑是否可以套用这种方法。
定义 tofa[i] 表示 i节点到根节点的tow- path 的最大价值。
考虑是否能够通过dp 在O(n)的时间内求出这个状态
直接转移显然不好转移。
那么,在考虑这种问题的最简情况,既 只有一个点的情况。
定义toson[i] 表示以i为起点和终点的最大价值tow-path
显然 求toson数组是一个很经典的DP问题,两遍dfs即可。
求出toson后 就可以通过toson来得到tofa。 也是很好想到的。
那么这道题差不多就做完了。
细节看代码。

#include<bits/stdc++.h>
using namespace std;
const int MAX=3e5+10;
class Edge{
public:
    int v,next;
    long long w;
};
int n;
int tot;
int head[MAX];
Edge edge[MAX<<1];
long long values[MAX];
void init(){
    memset(head,-1,sizeof head);
    tot=0;
}
void add(int u,int v,int w){
    edge[tot].v=v;
    edge[tot].w=w;
    edge[tot].next=head[u];
    head[u]=tot;
    tot++;
}
long long toson[MAX];// 以i为节点的子树 以i为起点和终点 走tow-path的最大价值
void dfs1(int u,int pre){
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(v==pre){
            continue;
        }
        dfs1(v,u);
        toson[u]+=max(0ll,toson[v]-edge[i].w*2);
    }
    toson[u]+=values[u];
}
long long tofa[MAX];
void dfs2(int u,int pre){
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(v==pre){
            continue;
        }
        tofa[v]=toson[v]+tofa[u]-max(0ll,toson[v]-edge[i].w*2)-edge[i].w;
        toson[v]+=max(0ll,toson[u]-max(0ll,toson[v]-edge[i].w*2)-2*edge[i].w);
        dfs2(v,u);
    }
}
//<----------------------------- LCA -------------------------------->
int deep[MAX];
int fa[MAX][23];
int dfs3(int u,int _deep,int _fa){
    deep[u]=_deep;
    fa[u][0]=_fa;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(v==_fa){
            continue;
        }
        dfs3(v,_deep+1,u);
    }
}
void presove(){
    dfs3(1,0,1);
    for(int i=1;i<=20;i++){
        for(int j=1;j<=n;j++){
            fa[j][i]=fa[fa[j][i-1]][i-1];
        }
    }
}
int LCA(int u,int v){
    while(deep[u]!=deep[v]){
        if(deep[u]<deep[v]){
            swap(u,v);
        }
        int d= deep[u] -deep[v];
        for(int i=0;i<=20;i++){
            if(d>>i &1) u=fa[u][i];
        }
    }
    if(u==v) return u;
    for(int i=20;i>=0;i--){
        if(fa[u][i]!=fa[v][i]){
            u=fa[u][i];
            v=fa[v][i];
        }
    }
    return fa[u][0];
}
int main(){
    int q;
    scanf("%d %d",&n,&q);
    init();
    for(int i=1;i<=n;i++){
        scanf("%lld",&values[i]);
    }
    for(int i=1;i<n;i++){
        int u,v,w;
        scanf("%d %d %d",&u,&v,&w);
        add(u,v,w);
        add(v,u,w);
    }
    dfs1(1,-1);
    dfs2(1,-1);
    presove();
    while(q--){
        int u,v;
        scanf("%d %d",&u,&v);
        int fas=LCA(u,v);
        printf("%lld\n",tofa[u]+tofa[v]-2ll*tofa[fas]+toson[fas]);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值