给一棵点带权树,$q$次询问,问树上$x$到$y$路径上,两点权之差(后面的减去前面的)的最大值。


这个是在树链上找点,如果沿路径的最小值在最大值之前出现那肯定答案就是$maxx-minx$,但是反之就不好办了。。

方法一:在线倍增合并答案

先来看一个退化成链的情况:区间$ql,qr$内找$i<j$使$A_j-A_i$值最大怎么做。

这里尝试线段树解决。假设两个小区间合并答案的话,维护一个$dif_i$表示区间$i$上述答案。

那么合并区间答案时,要么答案出自左半区间,要么右半区间,要么跨中间,一个取左边的另一个取右边的,贪心可知取左半区间min和右半区间max。

这个其实就是一个对不同情况答案的分类讨论,看来我分类来取最佳答案的思想还不够啊,区间最大连续和不也是同一类型的么。

然后由启发,放到树上,于是一条树链就可以通过lca左边、lca右边、lca两边两条链的min和max来求得答案。

维护树链上信息可以有树剖等,但是这里发现使用倍增维护答案时,向上跳lca时的各小链的答案是具有合并性的,树剖的话就繁掉了。

于是倍增维护$upw[i][k],dow[i][k]$,表示从$i$向上跳$2^k$步的答案,以及从上面的$2^k$距离处跳下来的答案。

跳的时候每跳一段,为了和之前已经跳的一大段合并答案,取之前一大段的min和当前这一段的max作差比较更新,思路其实和线段树差不多。

因为我比较懒,所以没太想多少,写的常数比较大。

复杂度是$O(nlogn)$的。

【注意思路】分类讨论总结答案!根据维护信息的性质选择维护方法!

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define dbg(x) cerr << #x << " = " << x <<endl
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int,int> pii;
template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;}
template<typename T>inline T _min(T A,T B){return A<B?A:B;}
template<typename T>inline T _max(T A,T B){return A>B?A:B;}
template<typename T>inline T _max(T A,T B,T C){return _max(_max(A,B),C);}
template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,):;}
template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,):;}
template<typename T>inline T read(T&x){
x=;int f=;char c;while(!isdigit(c=getchar()))if(c=='-')f=;
while(isdigit(c))x=x*+(c&),c=getchar();return f?x=-x:x;
}
const int N=+,INF=0x3f3f3f3f;
int n,q,qx,qy,_pow[N];
//edge
int Head[N],A[N],tot;
struct thxorz{int to,nxt;}G[N<<];
inline void Addedge(int x,int y){
G[++tot].to=y,G[tot].nxt=Head[x],Head[x]=tot;
G[++tot].to=x,G[tot].nxt=Head[y],Head[y]=tot;
}
//lca
int f[N][],minv[N][],maxv[N][],upw[N][],dow[N][],depth[N];
#define y G[j].to
void dfs(int x,int fa){
f[x][]=fa,minv[x][]=maxv[x][]=A[x],upw[x][]=dow[x][]=;
for(register int i=;i<=_pow[depth[x]];++i){
f[x][i]=f[f[x][i-]][i-];
minv[x][i]=_min(minv[x][i-],minv[f[x][i-]][i-]);
maxv[x][i]=_max(maxv[x][i-],maxv[f[x][i-]][i-]);
upw[x][i]=_max(upw[x][i-],upw[f[x][i-]][i-],maxv[f[x][i-]][i-]-minv[x][i-]);
dow[x][i]=_max(dow[x][i-],dow[f[x][i-]][i-],maxv[x][i-]-minv[f[x][i-]][i-]);
}
for(register int j=Head[x];j;j=G[j].nxt)if(y^fa)depth[y]=depth[x]+,dfs(y,x);
}
#undef y
inline int LCA(int x,int y){
if(depth[x]<depth[y])_swap(x,y);
for(register int i=_pow[depth[x]-depth[y]];~i;--i)
if(depth[f[x][i]]>=depth[y])
x=f[x][i];
if(x==y)return y;
for(register int i=_pow[depth[x]];~i;--i)
if(f[x][i]^f[y][i])
x=f[x][i],y=f[y][i];
return f[x][];
}
inline int Query(int x,int y){
int lca=LCA(x,y),ans=,minx=INF,maxx=;//dbg(lca);
for(register int i=_pow[depth[x]-depth[lca]];~i;--i)
if(depth[f[x][i]]>=depth[lca])
MAX(ans,upw[x][i]),MAX(ans,maxv[x][i]-minx),MIN(minx,minv[x][i]),x=f[x][i];
MIN(minx,A[lca]);
for(register int i=_pow[depth[y]-depth[lca]];~i;--i)
if(depth[f[y][i]]>=depth[lca])
MAX(ans,dow[y][i]),MAX(ans,maxx-minv[y][i]),MAX(maxx,maxv[y][i]),y=f[y][i];
MAX(maxx,A[lca]);
return _max(ans,maxx-minx);
} int main(){//freopen("test.in","r",stdin);//freopen("test.out","w",stdout);
for(register int i=;i<=5e4;++i)_pow[i]=__lg(i);
read(n);for(register int i=;i<=n;++i)read(A[i]);
for(register int i=;i<n;++i)read(qx),read(qy),Addedge(qx,qy);
depth[]=,dfs(,);read(q);
while(q--)read(qx),read(qy),printf("%d\n",Query(qx,qy));
return ;
}

方法二:tarjan+带权并查集离线处理询问(☆)

由于之前没学过tarjan离线LCA,于是把这个东西学了一下。STO tarjan ORZ。具体的学习地址放在了转载内容中了。还是很好理解的。

然后看这题,让我对tarjan算法有了进一步的认识,也就是基于并查集的改造维护一些信息。

首先基本思路还是一样的。是把答案拆成lca左边链的答案和右边链的答案加上跨过lca的答案取max,然后求点到lca链的信息(upw,dow,max,min)不再用倍增处理了。

采用lca自底向上逐步合并答案的方法。

由于信息的可合并性,考虑用离线LCA,同时并查集带权,维护上述信息,该点到此时的该点ancestor一条长链的信息就可以查询并查集并在回去时维护好即可。

注意到一个点作为LCA的时候,其询问的点对一定在子树中,等我把子树全dfs好了(dfs时把lca记下来,详见code),最后再处理以子树根为LCA的点对答案,将询问点对在并查集中路径压缩一下,合并信息。

然后就离线处理了答案。详见code。个人觉得还蛮清楚的。

复杂度$O(n+q \alpha (n))$,忽略反阿克曼函数,量级就是$O(n)$的。快了许多。


由于写这题的时候环境较吵,写了许多错误,调了半小时,也表明功底不扎实啊。

记录一下错误:

  • line41:注意到并查集压缩路径时,ancestor是先变掉的,所以更新信息时要保存父亲,递归完更新。
  • line46:打错minv,醉了。。
  • line43~46:注意并查集合并、更新信息的顺序,不能先更新minv、maxv,不然会把新的minv、maxv覆盖在upw、dow上面。
  • line55:注意得到正确的询问顺序以及询问序号,并且line56记得再维护一下点对信息再算答案。
  • line59:别漏了vis。。。

总之这种做法还是很重要的。两点启示:1.维护lca链的相关可合并信息,可以用离线处理lca带权并查集维护。2.多个维护信息要注意更新顺序。

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define dbg(x) cerr << #x << " = " << x <<endl
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int,int> pii;
template<typename T>inline T _min(T A,T B){return A<B?A:B;}
template<typename T>inline T _max(T A,T B){return A>B?A:B;}
template<typename T>inline T _max(T A,T B,T C){return _max(_max(A,B),C);}
template<typename T>inline T _min(T A,T B,T C){return _min(_min(A,B),C);}
template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,):;}
template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,):;}
template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;}
template<typename T>inline T read(T&x){
x=;int f=;char c;while(!isdigit(c=getchar()))if(c=='-')f=;
while(isdigit(c))x=x*+(c&),c=getchar();return f?x=-x:x;
}
const int N=5e4+;
int n,m,ans[N];
struct thxorz{int to,nxt;}G[N<<],Q[N<<],lca[N];
int Head[N],Qhead[N],lhead[N],tot=;
inline void Addedge(int x,int y){
G[++tot].to=y,G[tot].nxt=Head[x],Head[x]=tot;
G[++tot].to=x,G[tot].nxt=Head[y],Head[y]=tot;
}
inline void AddQuery(int x,int y){
Q[++tot].to=y,Q[tot].nxt=Qhead[x],Qhead[x]=tot;
Q[++tot].to=x,Q[tot].nxt=Qhead[y],Qhead[y]=tot;
}
inline void Addlca(int x,int y){
lca[++tot].to=y,lca[tot].nxt=lhead[x],lhead[x]=tot;
} struct disjoint_set{int maxv,minv,upw,dow,anc;}T[N];
inline int Find_anc(int x){
if(T[x].anc==x)return x;
int tmp=T[x].anc;
T[x].anc=Find_anc(T[x].anc);
T[x].upw=_max(T[x].upw,T[tmp].upw,T[tmp].maxv-T[x].minv);
T[x].dow=_max(T[x].dow,T[tmp].dow,T[x].maxv-T[tmp].minv);
T[x].maxv=_max(T[x].maxv,T[tmp].maxv);
T[x].minv=_min(T[x].minv,T[tmp].minv);//attetion:the order.
return T[x].anc;
}
#define y G[j].to
int vis[N];
void tarjan(int x,int fa){
for(register int j=Head[x];j;j=G[j].nxt)if(y^fa)tarjan(y,x),T[y].anc=x;
for(register int j=Qhead[x];j;j=Q[j].nxt)if(vis[Q[j].to])Addlca(Find_anc(Q[j].to),j>>);
for(register int j=lhead[x];j;j=lca[j].nxt){
int a=Q[(lca[j].to<<)^].to,b=Q[lca[j].to<<].to;
Find_anc(a),Find_anc(b);
ans[lca[j].to]=_max(T[a].upw,T[b].dow,T[b].maxv-T[a].minv);
}
vis[x]=;
}
#undef y
int main(){//freopen("test.in","r",stdin);//freopen("test.out","w",stdout);
read(n);
for(register int i=,x;i<=n;++i)read(x),T[i].anc=i,T[i].minv=T[i].maxv=x,T[i].dow=T[i].upw=;
for(register int i=,x,y;i<n;++i)read(x),read(y),Addedge(x,y);
read(m);tot=;
for(register int i=,x,y;i<=m;++i)read(x),read(y),AddQuery(x,y);
tot=;tarjan(,);
for(register int i=;i<=m;++i)printf("%d\n",ans[i]);
return ;
}

poj3728 The merchant[倍增]的更多相关文章

  1. POJ3728The merchant (倍增)(LCA)(DP)(经典)(||并查集压缩路径?)

    There are N cities in a country, and there is one and only one simple path between each pair of citi ...

  2. POJ3728 The merchant解题报告

    Description There are N cities in a country, and there is one and only one simple path between each ...

  3. poj 3728 The merchant 倍增lca求dp

    题目: zdf给出的题目翻译: 从前有一个富饶的国度,在这里人们可以进行自由的交易.这个国度形成一个n个点的无向图,每个点表示一个城市,并且有一个权值w[i],表示这个城市出售或收购这个权值的物品.又 ...

  4. [POJ3728]The merchant

    题目大意: 给你一棵n个结点的带权树,有q组询问,问你从u到v的路径上最大值与最小值的差(最大值在最小值后面). 思路: 首先考虑路径上合并两个子路径u->t和t->v时的情况. 假设我们 ...

  5. POJ3728 THE MERCHANT LCA RMQ DP

    题意简述:给定一个N个节点的树,1<=N<=50000 每个节点都有一个权值,代表商品在这个节点的价格.商人从某个节点a移动到节点b,且只能购买并出售一次商品,问最多可以产生多大的利润. ...

  6. [POJ3728]The merchant(tanrjan_lca + DP)

    传送门 比着题解写还错... 查了两个小时没查出来,心态爆炸啊 以后再查 ——代码(WA) #include <cstdio> #include <cstring> #incl ...

  7. poj3728The merchant 【倍增】【LCA】

    There are N cities in a country, and there is one and only one simple path between each pair of citi ...

  8. poj3728 倍增法lca 好题!

    lca的好题!网上用st表和离线解的比较多,用树上倍增也是可以做的 不知道错在哪里,等刷完了这个专题再回来看 题解链接https://blog.csdn.net/Sd_Invol/article/de ...

  9. [POJ 3728]The merchant

    Description There are N cities in a country, and there is one and only one simple path between each ...

随机推荐

  1. 【Python开发】python使用urllib2抓取防爬取链接

    前几天刚看完<Linux/Unix设计思想>,真是一本不错的书,推荐想提高自己代码质量的童鞋看一下,里面经常提到要以小为美,一个程序做好一件事,短小精悍,因此我也按照这种思想来写pytho ...

  2. webpack打包vue项目之后怎么启动&注意事项

    参考路径:https://blog.csdn.net/cn_yaojin/article/details/80164477 参考路径:https://www.imooc.com/article/323 ...

  3. hadoop的单机配置

    hadoop的单机配置 准备工作 利用vim /etc/sysconfig/network命令修改主机名称. Ssh security shell 远程登录 登录远程服务器 $ ssh user@ho ...

  4. Python学习教程:Pandas中第二好用的函数

    从网上看到一篇好的文章是关于如何学习python数据分析的迫不及待想要分享给大家,大家也可以点链接看原博客.希望对大家的学习有帮助. 本次的Python学习教程是关于Python数据分析实战基础相关内 ...

  5. JavaSE编码试题强化练习1

    1. 编写应用程序,创建类的对象,分别设置圆的半径.圆柱体的高,计算并分别显示圆半径.圆面积.圆周长,圆柱体的体积. /** * 定义父类--圆类 */ public class Circle { / ...

  6. 【Python】【demo实验3】【显示素数,显示两个数范围内的所有素数】

    打印两个整数之间的所有素数: (使用平方根来判断  是否应停止验证该数值是否为素数) for i in range(956253526252,9956253526252): k = 1 if i == ...

  7. 设计模式:策略模式(Stratege)

    首先我们需要知道策略模式与状态模式是如此的相似,就犹如一对双胞胎一样.只不过状态模式是通过改变对象内部的状态来帮助对象控制自己的行为,而策略模式则是围绕可以互换的算法来创建成功业务的.两者都可用于解决 ...

  8. 2019中山纪念中学夏令营-Day4[JZOJ]

    Begin (题目的排序方式:难易程度) 什么是对拍: 对拍是一种在写完程序后,验证自己程序是不是正解的比较方便的方法. 实现过程: 对同一道题,再打一个暴力程序,然后用一些大数据等跑暴力程序来进行验 ...

  9. python实现更换电脑桌面壁纸,锁屏,文件加密方式

    python实现更换壁纸和锁屏代码 #控制windows系统 import win32api,win32con,win32gui # 可以利用python去调用dll动态库的包.嵌入式开发 from ...

  10. 网络信息统计netstat|ss|ip

    1:netstate[弃用] netstat的作用: 需求 原命令 新命令 1:网络连接 netstat -a ss 2:路由表 netstat -r ip route 3:统计接口 netstat ...