lca题目 敌对势力 题解

1 篇文章 0 订阅

这是计蒜客noi模拟赛的day1第二题,现在写完了,发一波题解。
先上题目:敌对势力
这一题先想到的是暴力暴力出奇迹 ,后面只得了30分,然后后面老师说是lca我当场懵逼 ,为什么?原来是看错了,我以为是一个图,这当然不可以用lca,废话 ,后面才看到是无根树,眼瞎害死人
讲正事,看到无根树我们首先看看可不可以转成有根树,而这一题刚好可以,因为题目无特殊要求,所以我们可以随便选个点作为根,我们优先选择1,选择的原则是能简单就简单,不要给自己找麻烦。转换成有根树后我们再说说怎么用lca。
题目给我们两条路,我们画图自己算了一下后发现好像有一个特点:如果两条路有交点的话,那么这两条路中必有一条的lca在另一条上,为什么?因为这是一个树。后面我们自己证明了一下,还真是,至于证明因为简单和难以阐述(是你自己不会说吧 )所以就大家自己在草稿纸上画画图,都能算出来,证明讲完了就是求lca了,在此我们用倍增求lca,倍增应该不用讲吧,所以我们直接用了。求出lca,我们就要去check了,我们暴力把路径分为两条,分别是从lca到两个端点。这样我们直接暴力枚举check就好了。
上代码:

#include<iostream>
#include<cstdio>
#include<queue>
#include<cmath> 
#include<cstring>
using namespace std;
int Next[200100],head[200100],ver[200100],size=0,d[100100],f[100100][30],t;
queue <int> q;//倍增常规队列 
void add(int x,int y)//邻接表操作 
{
	size++;
	Next[size]=head[x];
	head[x]=size;
	ver[size]=y;
	return ;
}
int lca(int x,int y)//倍增查询 
{
	if(d[x]>d[y])
		swap(x,y);
	for(int i=t;i>=0;i--)
		if(d[f[y][i]]>=d[x])
			y=f[y][i];
	if(x==y)
		return x;
	for(int i=t;i>=0;i--)
	{
		if(f[x][i]!=f[y][i])
		{
			x=f[x][i];
			y=f[y][i];
		}
	}
	return f[x][0];
}
void bfs()//倍增预处理 
{
	q.push(1);
	d[1]=1;
	while(q.size())
	{
		int x=q.front();
		q.pop();
		for(int i=head[x];i;i=Next[i])
		{
			int y=ver[i];
			if(d[y])
				continue;
			d[y]=d[x]+1;
			f[y][0]=x;
			for(int j=1;j<=t;j++)
				f[y][j]=f[f[y][j-1]][j-1];
			q.push(y);
		}
	}
	return ;
}
bool check(int fa,int x,int y)//暴力查找 
{
	if(x==fa)
		return true;
	while(x!=y)
	{
		x=f[x][0];
		if(fa==x)
			return true;
	}
	return false;
}
int main()
{
	int n,m;
	memset(d,0,sizeof(d));
	scanf("%d %d",&n,&m);
	t=(int)((log(n)/log(2))+1);
	for(int i=1;i<n;i++)//读入 
	{
		int u,v;
		scanf("%d %d",&u,&v);
		add(u,v);
		add(v,u);
	}
	bfs();
	for(int i=1;i<=m;i++)//询问 
	{
		int a,b,c,e,fa1,fa2;
		scanf("%d %d %d %d",&a,&b,&c,&e);
		fa1=lca(a,b);//找到两条路的lca 
		fa2=lca(c,e);
		if(d[fa2]>d[fa1])//减个枝,因为我们发现一定是深度大的在另一条路上 
		{
			if(check(fa2,a,fa1) || check(fa2,b,fa1))//有碰到为真
				printf("NO \n");
			else printf("YES \n");
		}
		else 
		{
			if(check(fa1,c,fa2) || check(fa1,e,fa2))//有碰到为真
				printf("NO \n");
			else printf("YES \n");
		}
	}
	return 0;
 } 

一提交发现只有70分,为什么?方法错了吗,其实不是,只是后面check的时候错了,我们的check的时间复杂度是O(n),看起来很小,可是题目承受不了,所以我们要换一下,通过画图我们发现如果有一个点在路径上那么这个点分别和路径的两个端点的lca一定有一个是它本身,这个很好证明,就大家自己画画图证明一下,这样我们就可以降到(logn),这样就ojbk了,下面上代码:

#include<iostream>
#include<cstdio>
#include<queue>
#include<cmath> 
#include<cstring>
using namespace std;
int Next[200100],head[200100],ver[200100],size=0,d[100100],f[100100][30],t;
queue <int> q;
void add(int x,int y)//邻接表尝龟操作 
{
   size++;
   Next[size]=head[x];
   head[x]=size;
   ver[size]=y;
   return ;
}
int lca(int x,int y)//倍增lca查询 
{
   if(d[x]>d[y])
   	swap(x,y);
   for(int i=t;i>=0;i--)
   	if(d[f[y][i]]>=d[x])
   		y=f[y][i];
   if(x==y)
   	return x;
   for(int i=t;i>=0;i--)
   {
   	if(f[x][i]!=f[y][i])
   	{
   		x=f[x][i];
   		y=f[y][i];
   	}
   }
   return f[x][0];
}
void bfs()//预处理一波 
{
   q.push(1);
   d[1]=1;
   while(q.size())
   {
   	int x=q.front();
   	q.pop();
   	for(int i=head[x];i;i=Next[i])
   	{
   		int y=ver[i];
   		if(d[y])
   			continue;
   		d[y]=d[x]+1;
   		f[y][0]=x;
   		for(int j=1;j<=t;j++)
   			f[y][j]=f[f[y][j-1]][j-1];
   		q.push(y);
   	}
   }
   return ;
}
int main()
{
   int n,m;
   memset(d,0,sizeof(d));
   scanf("%d %d",&n,&m);
   t=(int)((log(n)/log(2))+1);
   for(int i=1;i<n;i++)
   {
   	int u,v;
   	scanf("%d %d",&u,&v);
   	add(u,v);
   	add(v,u);
   }
   bfs();
   for(int i=1;i<=m;i++)
   {
   	int a,b,c,e,fa1,fa2;
   	scanf("%d %d %d %d",&a,&b,&c,&e);
   	fa1=lca(a,b);
   	fa2=lca(c,e);
   	if(d[fa2]<d[fa1])//稍稍减个枝 
   	{
   		if(lca(fa1,c)==fa1 || lca(fa1,e)==fa1)//有碰到为真
   			printf("NO \n");
   		else printf("YES \n");
   	}
   	else
   	{
   		if(lca(fa2,a)==fa2 || lca(fa2,b)==fa2)//有碰到为真
   			printf("NO \n");
   		else printf("YES \n");
   	}
   }
   return 0;
} 

这样这一题就AC了,希望这篇题解对大家有帮助,也希望大家可以动手打打代码,不要直接复制。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值