POJ 3728 The merchant(LCA+DP)

87 篇文章 0 订阅

The merchant
Time Limit: 3000MS Memory Limit: 65536K
Total Submissions: 5160 Accepted: 1776
Description

There are N cities in a country, and there is one and only one simple path between each pair of cities. A merchant has chosen some paths and wants to earn as much money as possible in each path. When he move along a path, he can choose one city to buy some goods and sell them in a city after it. The goods in all cities are the same but the prices are different. Now your task is to calculate the maximum possible profit on each path.

Input

The first line contains N, the number of cities.
Each of the next N lines contains wi the goods’ price in each city.
Each of the next N-1 lines contains labels of two cities, describing a road between the two cities.
The next line contains Q, the number of paths.
Each of the next Q lines contains labels of two cities, describing a path. The cities are numbered from 1 to N.

1 ≤ N, wi, Q ≤ 50000

Output

The output contains Q lines, each contains the maximum profit of the corresponding path. If no positive profit can be earned, output 0 instead.

Sample Input

4
1
5
3
2
1 3
3 2
3 4
9
1 2
1 3
1 4
2 3
2 1
2 4
3 1
3 2
3 4
Sample Output

4
2
2
0
0
0
0
2
0
Source

POJ Monthly Contest – 2009.04.05, GaoYihan

题目大意

  有一棵树,每个结点有一个物品的价值。有一些询问,问在从一个点到另一个点了路径上,先在一个地方买,再在一个地方卖的最大获利。

解题思路

  令从 u v的LCA为 x ,那么答案一定是从u x 的最大获利,从x v 的最大获利,x v 的最大值减去u x <script type="math/tex" id="MathJax-Element-440">x</script>的最小值。于是我们就可以在求LCA的过程中DP一下得到上面所需的东西。
  用离线Tarjan实现时在并查集路径压缩时进行DP,用倍增实现时直接在倍增数组上进行DP。

离线Tarjan写法

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <cmath>
#include <cstdlib>
using namespace std;
#define INF 0x3f3f3f3f
#define fi first
#define se second

const int MAXN=50000+3;
int V, Q, val[MAXN];
vector<int> G[MAXN];
vector<pair<int, int> > q[MAXN];// id, other
vector<pair<int, int> > ans[MAXN];// to, it
int up[MAXN], down[MAXN];//当前点到lca的最大获利,lca到当前点点最大获利
int max_val[MAXN], min_val[MAXN];//当前点到lca的最大/最小价格
int par[MAXN];
bool vis[MAXN];
int res[MAXN];

int findfather(int x)//并查集查询,同时进行dp
{
    if(par[x]==x)
        return x;
    int fa=par[x];
    par[x]=findfather(par[x]);
    up[x]=max(up[x], max(up[fa], max_val[fa]-min_val[x]));
    down[x]=max(down[x], max(down[fa], max_val[x]-min_val[fa]));
    max_val[x]=max(max_val[x], max_val[fa]);
    min_val[x]=min(min_val[x], min_val[fa]);
    return par[x];
}

void tarjan(int u)
{
    par[u]=u;
    vis[u]=true;
    for(int i=0;i<q[u].size();++i)//把查询保存到lca处
    {
        int v=q[u][i].se;
        if(vis[v])
        {
            int lca=findfather(v);
            ans[lca].push_back(make_pair(u, i));
        }
    }
    for(int i=0;i<G[u].size();++i)
    {
        int v=G[u][i];
        if(!vis[v])
        {
            tarjan(v);
            par[v]=u;
        }
    }
    for(int i=0;i<ans[u].size();++i)//处理以当前结点为lca的所有查询
    {
        int x=ans[u][i].fi, y=q[x][ans[u][i].se].se;
        int id=q[x][ans[u][i].se].fi;
        if(id<0)
        {
            id=-id;
            swap(x, y);
        }
        findfather(x);
        findfather(y);
        res[id]=max(up[x], down[y]);
        res[id]=max(res[id], max_val[y]-min_val[x]);
    }
}


int main()
{
    scanf("%d", &V);
    for(int i=1;i<=V;++i)
    {
        scanf("%d", &val[i]);
        max_val[i]=min_val[i]=val[i];
    }
    for(int i=1;i<V;++i)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    scanf("%d", &Q);
    for(int i=1;i<=Q;++i)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        q[u].push_back(make_pair(i, v));
        q[v].push_back(make_pair(-i, u));
    }
    tarjan(1);
    for(int i=1;i<=Q;++i)
        printf("%d\n", res[i]);

    return 0;
}

倍增写法

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <string>
#include <ctime>
#include <bitset>
using namespace std;
#define INF 0x3f3f3f3f
#define ULL unsigned long long
#define LL long long
#define fi first
#define se second
#define mem(a, b) memset((a),(b),sizeof(a))
#define sqr(x) ((x)*(x))

const int MAXN=50000+3;
const int MAXLOG=17;

int N, Q, price[MAXN];
vector<int> G[MAXN];
int dp_max[MAXLOG][MAXN], dp_min[MAXLOG][MAXN];//向上走2^k步之间的最高与最低价格
int dp_up[MAXLOG][MAXN], dp_down[MAXLOG][MAXN];//从u向上走2^k/向下走2^k步到u 的最大利润
int parent[MAXLOG][MAXN];//向上走2^k步到达的点(超过根时记为-1)
int depth[MAXN];

void dfs(int u, int fa, int deep)
{
    parent[0][u]=fa;
    depth[u]=deep;
    dp_up[0][u]=max(price[fa]-price[u], 0);
    dp_down[0][u]=max(price[u]-price[fa], 0);
    dp_max[0][u]=max(price[u], price[fa]);
    dp_min[0][u]=min(price[u], price[fa]);
    for(int i=0;i<G[u].size();++i)
        if(G[u][i]!=fa)
            dfs(G[u][i], u, deep+1);
}

void pre_work()
{
    mem(dp_max, 0);
    mem(dp_min, 0x3f);
    dfs(0, -1, 0);
    for(int k=0;k+1<MAXLOG;++k)
    {
        for(int u=0;u<N;++u)
        {
            if(parent[k][u]<0)
                parent[k+1][u]=-1;
            else
            {
                parent[k+1][u]=parent[k][parent[k][u]];
                int mid=parent[k][u];
                dp_max[k+1][u]=max(dp_max[k][u], dp_max[k][mid]);
                dp_min[k+1][u]=min(dp_min[k][u], dp_min[k][mid]);
                dp_up[k+1][u]=max(max(dp_up[k][u], dp_up[k][mid]), dp_max[k][mid]-dp_min[k][u]);
                dp_down[k+1][u]=max(max(dp_down[k][u], dp_down[k][mid]), dp_max[k][u]-dp_min[k][mid]);
            }
        }
    }
}

int lca(int u, int v)
{
    if(depth[u]>depth[v])
        swap(u, v);
    for(int k=0;k<MAXLOG;++k)
        if((depth[v]-depth[u])>>k&1)
            v=parent[k][v];
    if(u==v)
        return u;
    for(int k=MAXLOG-1;k>=0;--k)
        if(parent[k][u]!=parent[k][v])
        {
            u=parent[k][u];
            v=parent[k][v];
        }
    return parent[0][u];
}


int up(int u, int k, int &the_min)
{
    the_min=INF;
    int res=0, pre_min_price=INF;
    for(int i=MAXLOG-1;i>=0;--i)
        if(k>>i&1)
        {
            the_min=min(the_min, dp_min[i][u]);
            res=max(res, dp_up[i][u]);
            res=max(res, dp_max[i][u]-pre_min_price);
            pre_min_price=min(pre_min_price, dp_min[i][u]);
            u=parent[i][u];
        }
    return res;
}

int down(int u, int k, int &the_max)
{
    the_max=0;
    int res=0, pre_max_price=0;
    for(int i=MAXLOG-1;i>=0;--i)
        if(k>>i&1)
        {
            the_max=max(the_max, dp_max[i][u]);
            res=max(res, dp_down[i][u]);
            res=max(res, pre_max_price-dp_min[i][u]);
            pre_max_price=max(pre_max_price, dp_max[i][u]);
            u=parent[i][u];
        }
    return res;
}

int main()
{
    scanf("%d", &N);
    for(int i=0;i<N;++i)
        scanf("%d", &price[i]);
    for(int i=1;i<N;++i)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        --u;
        --v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    pre_work();
    scanf("%d", &Q);
    while(Q--)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        --u;
        --v;
        int com=lca(u, v);//最近公共祖先
        int the_max, the_min;
        int up_profit=up(u, depth[u]-depth[com], the_min);
        int down_profit=down(v, depth[v]-depth[com], the_max);
        printf("%d\n", max(max(up_profit, down_profit), the_max-the_min));
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 给出一个$n\times m$的矩阵,每个位置上有一个非负整数,代表这个位置的海拔高度。一开始时,有一个人站在其中一个位置上。这个人可以向上、下、左、右四个方向移动,但是只能移动到海拔高度比当前位置低或者相等的位置上。一次移动只能移动一个单位长度。定义一个位置为“山顶”,当且仅当从这个位置开始移动,可以一直走到海拔高度比它低的位置上。请问,这个矩阵中最多有多少个“山顶”? 输入格式 第一行两个整数,分别表示$n$和$m$。 接下来$n$行,每行$m$个整数,表示整个矩阵。 输出格式 输出一个整数,表示最多有多少个“山顶”。 样例输入 4 4 3 2 1 4 2 3 4 3 5 6 7 8 4 5 6 7 样例输出 5 算法1 (递归dp) $O(nm)$ 对于这道题,我们可以使用递归DP来解决,用$f(i,j)$表示以$(i,j)$为起点的路径最大长度,那么最后的答案就是所有$f(i,j)$中的最大值。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码 算法2 (动态规划) $O(nm)$ 动态规划的思路与递归DP类似,只不过转移方程和实现方式有所不同。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值