POJ3728_The merchant_LCA + dp

题意

n 个城市构成一棵树,所有城市中都流通一种商品,但价格不同。一个商人从一个城市到另一个城市。在路上,他可以在一个城市中买一件商品,然后在这座城市后面的一座城市中卖掉,并赚取差价。给出起点和终点,问商人能得到的最大利润是多少?

思路

LCA + dp
原题解作者的链接
http://www.hankcs.com/program/algorithm/poj-3728-the-merchant.html
假设有路径 u … t … v,则 u 到 t 的最利润有三种可能:u 到 t 的最大利润、t 到 v 的最大利润、u 到 t 买, t 到 v 卖,即 t 到 v 的最大价格 - u 到 t 的最小价格。
详见代码。

链接

http://poj.org/problem?id=3728

代码

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>

using namespace std;

const int maxv = 5e4 + 10;
const int maxlogv = 16;
const int inf = 0x3f3f3f3f;

int n, q;

//输入
//树
vector<int> G[maxv];
//价格
int price[maxv];

//lca零件
//v向上走2^k步到达的祖先
int parent[maxlogv][maxv];
//v的深度
int depth[maxv];

//dp
//点v向上走2^k步中的最高价格   点v向上走2^k步中的最低价格
int dp_max[maxlogv][maxv], dp_min[maxlogv][maxv];
//点v向上走2^k步中的最大利润   点v向下走2^k步中的最大利润
int dp_up[maxlogv][maxv], dp_down[maxlogv][maxv];

//dfs初始化 父亲节点(走2^0步到达的祖先),深度,4个dp数组
//当前节点,父亲节点,深度
void dfs(int v, int p, int d){
    parent[0][v] = p;
    depth[v] = d;

    dp_up[0][v] = max(price[p] - price[v], 0);
    dp_down[0][v] = max(price[v] - price[p], 0);
    dp_max[0][v] = max(price[v], price[p]);
    dp_min[0][v] = min(price[v], price[p]);

    for(int i = 0; i < G[v].size(); i++){
        if(G[v][i] != p) dfs(G[v][i], v, d + 1);
    }
}

//初始化倍增lca数组
void init(int V){
    memset(dp_max, 0, sizeof dp_max);
    memset(dp_min, inf, sizeof dp_min);

    dfs(0, -1, 0);

    for(int k = 0; k + 1 < maxlogv; k++){
        for(int v = 0; v < V; v++){
            if(parent[k][v] < 0) parent[k + 1][v] = -1;
            else{
                parent[k + 1][v] = parent[k][parent[k][v]];

                int t = parent[k][v];
                dp_max[k + 1][v] = max(dp_max[k][v], dp_max[k][t]);
                dp_min[k + 1][v] = min(dp_min[k][v], dp_min[k][t]);
                dp_up[k + 1][v] = max(max(dp_up[k][v], dp_up[k][t]), dp_max[k][t] - dp_min[k][v]);
                dp_down[k + 1][v] = max(max(dp_down[k][v], dp_down[k][t]), dp_max[k][v] - dp_min[k][t]);
            }
        }
    }
}

//二分法求lca
int lca(int u, int v){
    if(depth[u] > depth[v]) swap(u, v);

    for(int k = 0; k < maxlogv; k++){
        if((depth[v] - depth[u]) >> k & 1){
            v = parent[k][v];
        }
    }

    if(u == v) return u;

    for(int k = maxlogv - 1; k >= 0; k--){
        if(parent[k][u] != parent[k][v]){
            u = parent[k][u];
            v = parent[k][v];
        }
    }

    return parent[0][u];
}

//向上走获得的最大利润
//出发节点,与lca的深度差,路上遇到的最小价格
int up(int x, int k, int &min_price){
    min_price = inf;
    int max_profit = 0;
    int prev_min_price = inf;

    for(int i = maxlogv - 1; i >= 0; i--){
        if(k >> i & 1){
            min_price = min(min_price, dp_min[i][x]);
            max_profit = max(max_profit, dp_up[i][x]);
            max_profit = max(max_profit, dp_max[i][x] - prev_min_price);
            prev_min_price = min(prev_min_price, dp_min[i][x]);
            x = parent[i][x];
        }
    }

    return max_profit;
}

//向下走的最大利润
//出发节点,与lca的深度差,路上遇到的最大价格
int down(int x, int k, int &max_price){
    max_price = 0;
    int max_profit = 0;
    int pre_max_price = 0;

    for(int i = maxlogv - 1; i >= 0; i--){
        if(k >> i & 1){
            max_price = max(max_price, dp_max[i][x]);
            max_profit = max(max_profit, dp_down[i][x]);
            max_profit = max(max_profit, pre_max_price - dp_min[i][x]);
            pre_max_price = max(pre_max_price, dp_max[i][x]);
            x = parent[i][x];
        }
    }

    return max_profit;
}

int main(){
    scanf("%d", &n);
    for(int i = 0; i < n; i++){
        scanf("%d", price + i);
    }
    for(int i = 0; i < n - 1; i++){
        int u, v;
        scanf("%d %d", &u, &v);
        u--, v--;
        G[u].push_back(v);
        G[v].push_back(u);
    }

    init(n);

    scanf("%d", &q);
    while(q--){
        int u, v;
        scanf("%d %d", &u, &v);
        u--, v--;

        int lv = lca(u, v);
        int max_price, min_price;
        int up_profit = up(u, depth[u] - depth[lv], min_price);
        int down_profit = down(v, depth[v] - depth[lv], max_price);
        int ans = max(max(up_profit, down_profit), max_price - min_price);
        printf("%d\n", ans);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值