链接
背景
(COI) (2006) , (2009.2.28) , (SP) (3978)
题意
给定 (n) 个点构成的一棵无根树, (m) 次询问 (x,y) 两点间的最长边和最短边。
解法
注意到给出的是一棵无根树,不妨设根为 (1) 号节点使其成为一棵有根树。对于每次询问,实际上只需要找出 (x) 到 (x,y) 的最近公共祖先路径上的最值以及 (y) 到 (x,y) 的最近公共祖先路径上的最值取最值即可。这与通过求最近公共祖先来求树上路径长度的过程有异曲同工之妙。
不妨设 (minn_{x,i}) 表示 (x) 节点向上跳 (2^i) 步后所有经过的边的最小值, (maxn_{x,i}) 表示 (x) 节点向上跳 (2^i) 步后所有经过的边的最大值,然后在 (bfs) 预处理倍增数组的同时预处理 (minn_{x,i}) 和 (maxn_{x,i}) 即可。
每次查询 (x,y) 的最近公共祖先时,在向上跳的过程中不断更新最值即可。
此题本质上就是在倍增法求最近公共祖先模板上套了一个取最值的运算。
细节
(1.) 请务必注意预处理 (minn_{x,i}) 和 (maxn_{x,i}) 的方法。以预处理 (minn_{x,i}) 为例,公式为 (minn_{x,i}=min { minn_{x,i-1},minn{f_{x,i-1},i-1} }) ,表示 (x) 节点向上跳 (2^{i-1}) 步经过所有边的最小值和 (x) 节点向上跳 (2^{i-1}) 步后的新节点 (x') 节点向上跳 (2^{i-1}) 步经过所有边的最小值取更小的值,里面的 (f_{x,i-1}) 表示的是点,而不是具体的某个数值。
(2.) 请注意查询 (x,y) 的最近公共祖先时更新最值的顺序。先用当前的节点更新权值,再向上跳。
(3.) 请注意查询 (x,y) 的最近公共祖先的最后一步还要从 (x,y) 两节点各跳一步,因此还要各更新一次最值。
代码
$View$ $Code$
```cpp
#include
using namespace std;
inline int read()
{
int ret=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
ret=(ret<<1)+(ret<<3)+ch-'0';
ch=getchar();
}
return ret*f;
}
int n,m,t,x,y,w,ans_min,ans_max;
int f[100005][18],d[100005],dis[100005],minn[100005][18],maxn[100005][18];
int num,head[200005];
queue q;
struct edge
{
int ver,nxt,w;
}e[200005];
inline void adde(int u,int v,int w)
{
e[++num].ver=v;
e[num].nxt=head[u];
e[num].w=w;
head[u]=num;
}
inline void bfs()
{
d[1]=1;
q.push(1);
while(!q.empty())
{
int x=q.front();
q.pop();
for(register int i=head[x];i;i=e[i].nxt)
{
int y=e[i].ver;
if(d[y])
continue;
d[y]=d[x]+1;
dis[y]=dis[x]+e[i].w;
f[y][0]=x;
minn[y][0]=e[i].w;
maxn[y][0]=e[i].w;
for(register int j=1;j<=t;j++)
{
f[y][j]=f[f[y][j-1]][j-1];
minn[y][j]=min(minn[y][j-1],minn[f[y][j-1]][j-1]);
maxn[y][j]=max(maxn[y][j-1],maxn[f[y][j-1]][j-1]);
}
q.push(y);
}
}
}
inline void lca(int x,int y)
{
if(d[x]