题目链接: http://poj.org/problem?id=3728
题意: 给出一棵带点权值的树, 对于 q 组形如 x, y 的询问, 一个人要从 x 到 y(单向), 他可以在路上任意一点以此点的的权值买一件物品, 并在接下来的路程中任意一点将其以该点的权值卖出, 输出其最大收益, 若不能获益则输出 0 .
思路: 若不考虑时间复杂度的话对于询问 x, y. 可以先求出 lca(x, y), 然后再 x -> lca -> y 路径上求一下最大收益即可. 然而这样会 tle.
可以考虑一下化简后面部分.
对于一组询问 x, y. 最大收益有 3 种情况:
1. x -> lca 过程中取得最大值
2. lca -> y 过程中取得最大值
3. lca -> y 中的最大权值减去 x -> lca 中的最小权值
为了方便处理这 3 种情况, 分别取
up 数组, up[x] 为 x -> lca 过程中能取得的最大值
dow 数组, dow[x] 为 lca -> x 过程中能取得的最大值
mi 数组, mi[x] 为 x -> lca 过程中最小权值
mx 数组, mx[x] 为 x -> lca 过程中最大权值
则第三种情况为: mx[y] - mi[x]
那么 x, y 的最大收益可以表示为: max(max(up[x], dow[y]), mx[y] - mi[x])
然而如果对于每一组 x, y 都更新一遍 up, dow, mi, mx 数组的话, 显然也是会 tle 的, 但是我们并没有必要那样做.
若对于询问 x, y, 我们已经的到了对应的 up, dow, mi, mx 数组, 那么对于 lca 在 lca(x, y) 上面的询问, 即对于 lca 为 lca(x, y) 祖先节点的询问 x1, x2,
假设 lca(x, y) 在 lca(x1, y1) 左子树中, 我们可以只更新 lca(x, y) 和 y1 到 lca(x1, y1) 对应的数组即可, 而无需更新 x1, y1 到 lca(x1, y1) 对应的数组.
如此, 我们可以按照询问的 lca 值从树的底层向上更新答案, 只需遍历一次树即可.
具体操作为; 在 tarjan 递归过程中先处理出所有询问的 lca, 然后在回溯时按 lca 自底向上更新答案即可.
代码:
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 using namespace std; 5 6 const int MAXN = 5e4 + 10; 7 struct node{ 8 int v, ip, next; 9 node(){}; 10 node(int V, int IP, int NEXT) : v(V), ip(IP), next(NEXT){}; 11 }edge1[MAXN << 1], edge2[MAXN << 1], edge3[MAXN << 1]; 12 13 int pre[MAXN], a[MAXN], b[MAXN]; 14 int head1[MAXN], head2[MAXN], head3[MAXN], id1, id2, id3; 15 int vis[MAXN], up[MAXN], dow[MAXN], mi[MAXN], mx[MAXN], sol[MAXN]; 16 17 void init(void){ 18 memset(vis, 0, sizeof(vis)); 19 memset(head1, -1, sizeof(head1)); 20 memset(head2, -1, sizeof(head2)); 21 memset(head3, -1, sizeof(head3)); 22 id1 = id2 = id3 = 0; 23 } 24 25 void addedge1(int u, int v, int ip){ 26 edge1[id1] = node(v, ip, head1[u]); 27 head1[u] = id1++; 28 } 29 30 void addedge2(int u, int v, int ip){ 31 edge2[id2] = node(v, ip, head2[u]); 32 head2[u] = id2++; 33 } 34 35 void addedge3(int u, int v, int ip){ 36 edge3[id3] = node(v, ip, head3[u]); 37 head3[u] = id3++; 38 } 39 40 int find(int v){//压缩路径并处理路径上的 up, dow, mi, mx 数组 41 if(v == pre[v]) return v;//到叶子节点或者下面的lca时停止递归 42 int fa = pre[v]; 43 pre[v] = find(pre[v]); 44 up[v] = max(max(up[v], up[fa]), mx[fa] - mi[v]);//以 v -> lca 为入边 45 dow[v] = max(max(dow[v], dow[fa]), mx[v] - mi[fa]);//以 lca -> v 为出边 46 mi[v] = min(mi[v], mi[fa]); 47 mx[v] = max(mx[v], mx[fa]); 48 return pre[v]; 49 } 50 51 void tarjan(int u){ 52 vis[u] = 1; 53 pre[u] = u; 54 for(int i = head2[u]; i != -1; i = edge2[i].next){ 55 int v = edge2[i].v; 56 int ip = edge2[i].ip; 57 if(vis[v]){ 58 int f = find(v); 59 addedge3(f, v, ip);//记录以 f 为 lca 的询问 60 } 61 } 62 for(int i = head1[u]; i != -1; i = edge1[i].next){ 63 int v = edge1[i].v; 64 if(!vis[v]){ 65 tarjan(v); 66 pre[v] = u; 67 } 68 } 69 for(int i = head3[u]; i != -1; i = edge3[i].next){//回溯时计算以 u 为 lca 的询问 70 int ip = edge3[i].ip; 71 find(a[ip]); 72 find(b[ip]); 73 sol[ip] = max(max(up[a[ip]], dow[b[ip]]), mx[b[ip]] - mi[a[ip]]); 74 } 75 } 76 77 int main(void){ 78 int n, x, y, q; 79 while(~scanf("%d", &n)){ 80 init(); 81 for(int i = 1; i <= n; i++){ 82 scanf("%d", &x); 83 up[i] = dow[i] = 0; 84 mi[i] = mx[i] = x; 85 } 86 for(int i = 1; i < n; i++){ 87 scanf("%d%d", &x, &y); 88 addedge1(x, y, i); 89 addedge1(y, x, i); 90 } 91 scanf("%d", &q); 92 for(int i = 0; i < q; i++){ 93 scanf("%d%d", &x, &y); 94 a[i] = x; 95 b[i] = y; 96 addedge2(x, y, i); 97 addedge2(y, x, i); 98 } 99 tarjan(1); 100 for(int i = 0; i < q; i++){ 101 printf("%d\n", sol[i]); 102 } 103 } 104 return 0; 105 }