【Vijos-P1935】不可思议的清晨-树上倍增+LCA+分类讨论
测试地址:不可思议的清晨
做法:我是按照这位前辈的做法做的:前辈的题解
里面有一句话我特别赞同:树上乱搞用倍增。事实证明,树上倍增在很多树的题目中都有很大用处,用了它,在往树根方向“走动”时时间效率可以从O(n)缩为O(log n),非常优秀。而在这个题目中,我们要分类讨论,设两个点x和y的LCA(最近公共祖先)为t,则分为3种情况:
1.x和y到t的距离相同,这个时候t就是区分离x近和离y近的点的分界点,这时候只要从x和y尽量往t走,又不至于走到t即可(也就是走到t的儿子),这时以它们为根的子树上点权的和就是答案。
2.x到t的距离比y到t的距离长,这时分界点一定在x到t的路径上,用倍增找出这个分界点,分界点的往x方向的点是离x较近的,书中其余的和x与y距离不相等的点就离y较近,用点权之和加加减减就可以了。
3.x到t的距离比y到t的距离短,同2,将x和y转换一下位置即可。
因此,我们只需先随便找一个树根,然后从树根DFS,记录:dep[x]:点x在树中的深度,倍增用;fa[x][i]:点x的第2^i代祖先,倍增用;d[x]:点x到树根的距离;s[x]:以x为根的子树上的点权之和。然后按照上述方法,对于每个询问分类讨论即可。
不过在抄题解之后还是有一点疑问,例如题目中数据范围指出边的长度可能为0,但在程序中好像没有体现分析这种情况的代码,不知是我水平比较低还是如何,如果有大神看到这个,求告知。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,q,tot=0,first[100010]={0},fa[100010][21]={0},dep[100010]={0};
ll val[100010],s[100010],d[100010];
struct edge {int v,next;ll d;} e[200010];
void insert(int a,int b,int c)
{
e[++tot].v=b;
e[tot].d=c;
e[tot].next=first[a];
first[a]=tot;
}
void dfs(int v)
{
s[v]=val[v];
for(int i=first[v];i;i=e[i].next)
if (e[i].v!=fa[v][0])
{
fa[e[i].v][0]=v;
dep[e[i].v]=dep[v]+1;
d[e[i].v]=d[v]+e[i].d;
dfs(e[i].v);
s[v]+=s[e[i].v];
}
}
int lca(int x,int y)
{
if (dep[x]<dep[y]) swap(x,y);
for(int i=19;i>=0;i--)
if (dep[fa[x][i]]>=dep[y]) x=fa[x][i];
for(int i=19;i>=0;i--)
if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
if (x==y) return x;
else return fa[x][0];
}
void work()
{
while(q--)
{
int x,y;
scanf("%d%d",&x,&y);
if (x==y) {printf("0 0\n");continue;}
int t=lca(x,y);
if (d[x]-d[t]==d[y]-d[t])
{
int tmpx,tmpy;
ll ansx,ansy;
tmpx=x;
for(int i=19;i>=0;i--)
if (dep[fa[tmpx][i]]>dep[t]) tmpx=fa[tmpx][i];
ansx=s[tmpx];
tmpy=y;
for(int i=19;i>=0;i--)
if (dep[fa[tmpy][i]]>dep[t]) tmpy=fa[tmpy][i];
ansy=s[tmpy];
printf("%lld %lld\n",ansx,ansy);
}
else if (d[x]-d[t]>d[y]-d[t])
{
int tmp,tmpx,tmpy;
ll ansx,ansy;
tmpx=x;
for(int i=19;i>=0;i--)
if (dep[fa[tmpx][i]]>dep[t]&&d[fa[tmpx][i]]+d[y]-(d[t]<<1)>d[x]-d[fa[tmpx][i]]) tmpx=fa[tmpx][i];
ansx=s[tmpx];
tmpy=fa[tmpx][0];
for(int i=19;i>=0;i--)
if (dep[fa[tmpy][i]]>dep[t]&&d[tmpy]==d[tmpx]) tmpy=fa[tmpy][i];
tmp=tmpx;
for(int i=19;i>=0;i--)
if (dep[fa[tmp][i]]>dep[tmpy]) tmp=fa[tmp][i];
ansy=s[1]-s[tmp];
printf("%lld %lld\n",ansx,ansy);
}
else
{
int tmp,tmpx,tmpy;
ll ansx,ansy;
tmpy=y;
for(int i=19;i>=0;i--)
if (dep[fa[tmpy][i]]>dep[t]&&d[fa[tmpy][i]]+d[x]-(d[t]<<1)>d[y]-d[fa[tmpy][i]]) tmpy=fa[tmpy][i];
ansy=s[tmpy];
tmpx=fa[tmpy][0];
for(int i=19;i>=0;i--)
if (dep[fa[tmpx][i]]>dep[t]&&d[tmpx]==d[tmpy]) tmpx=fa[tmpx][i];
tmp=tmpy;
for(int i=19;i>=0;i--)
if (dep[fa[tmp][i]]>dep[tmpx]) tmp=fa[tmp][i];
ansx=s[1]-s[tmp];
printf("%lld %lld\n",ansx,ansy);
}
}
}
int main()
{
scanf("%d",&n);
for(int i=1,a,b,c;i<n;i++)
{
scanf("%d%d%d",&a,&b,&c);
insert(a,b,c);insert(b,a,c);
}
for(int i=1;i<=n;i++) scanf("%lld",&val[i]);
scanf("%d",&q);
dep[1]=d[1]=0;
dfs(1);
for(int j=1;j<=19;j++)
for(int i=1;i<=n;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
work();
return 0;
}