# POJ 3728 The merchant（LCA+DP）

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$u$v$v$的LCA为x$x$，那么答案一定是从u$u$x$x$的最大获利，从x$x$v$v$的最大获利，x$x$v$v$的最大值减去u$u$x$x$的最小值。于是我们就可以在求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;
}