【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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值