MMSet2 题解

牛客网

题目描述:

不难发现课以转换成:给你一颗树, q q q次询问,每次询问给出一些点集,求点集中最大距离,输出 ( 最 大 距 离 + 1 ) / 2 (最大距离+1)/2 (+1)/2

方法一:

根据求树的直径的一种方法知道,将任意节点当成根,那么深度最大的一个点一定用到,接下来只要依次求出这个点到其它给出的点的距离,记录最大的即可。那么两点之间的距离如何求呢,其实就是这两个点的深度相加然后减去他们的2*LCA,这个也比较好理解。求LCA的方法这里就不在说明了,可以百度。

#include <bits/stdc++.h>
using namespace std;
int n,lv[300005],a[1000005],ans,fa[300005],son[300005],size[300005],top[300005]; 
vector<int>v[300005];
void dfs(int x,int f,int deep)
{
	fa[x]=f;
	lv[x]=deep;
	size[x]=1;
	for(int i=0;i<v[x].size();i++)
		if(v[x][i]!=f)
		{
			dfs(v[x][i],x,deep+1);
			size[x]+=size[v[x][i]];
			if(size[son[x]]<size[v[x][i]])
				son[x]=v[x][i];
		}
			
}
void dfs2(int x,int p)
{
	top[x]=p;
	if(son[x])dfs2(son[x],p);
	for(int i=0;i<v[x].size();i++)
		if(lv[v[x][i]]>lv[x]&&v[x][i]!=son[x])
			dfs2(v[x][i],v[x][i]);
}
int lca(int x,int y)
{	
	while(top[x]!=top[y])
	{
		if(lv[top[x]]<lv[top[y]])
			swap(x,y);
			x=fa[top[x]];
	}
	return lv[x]>lv[y]?y:x;
}
int dis(int x,int y)
{
	return lv[x]+lv[y]-2*lv[lca(x,y)];
}
int main(){
	int n;
	cin>>n;
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		v[x].push_back(y);
		v[y].push_back(x);
	} 
	dfs(1,-1,0);
	dfs2(1,1);
	int q;
	cin>>q;
	while(q--)
	{
		int k,x=-1;
		scanf("%d",&k);
		for(int i=0;i<k;i++)
		{
			scanf("%d",&a[i]);
			if(x==-1||lv[x]<lv[a[i]])
				x=a[i];
		}
			
		int ans=-1;
		for(int i=0;i<k;i++)
		{
			int c=dis(x,a[i]);
			if(c>ans)
				ans=c;
		}
		printf("%d\n",++ans/2);
	}
	return 0;
} 

方法二:

如果只有一次询问,那么我们就可以考虑DP来做这个题, d p ( i ) dp(i) dp(i)表示以 i i i为根的子树上的点符合条件(这个点是给出的点)的最大深度。当求到 i i i的时候我们可以计算以 i i i l c a lca lca的两点之间的最大距离,看能不能更新答案,就是最大的两个 d p ( k ) dp(k) dp(k)(其中 k k k i i i的儿子)相加减去( i i i的深度)* 2 2 2,要注意的是如果只有一个儿子符合条件并且点 i i i也是给出的点,还要求这两个点的距离,就是两个点的深度相减。
那么现在多组询问怎么办呢,可以用到虚树(这里是一个虚树的题目,和这个有些相似,写了下题解介绍了下虚树,有兴趣的同学可以看下)。

#include <bits/stdc++.h>
using namespace std;
const int maxn=3e5+5; 
const long long inf=1e18;
int lv[maxn],fa[maxn],son[maxn],size[maxn],top[maxn];//lca所需变量 
int dfsn[maxn],cnt,st[maxn],ans,vis[maxn];//虚树所需变量 
int n,a[1000005]; 
vector<int>v[maxn];
int dp(int x)
{
	if(v[x].size()==0)
		return lv[x];
	int p1=-1,p2=-1;
	for(int i=0;i<v[x].size();i++)
	{
		int c=dp(v[x][i]);
		if(c>p1)
		{
			p2=p1;
			p1=c;
		} 
		else if(c>p2)
			p2=c;
	} 
	if(p2!=-1)
		ans=max(ans,p1+p2-2*lv[x]);
	if(vis[x])ans=max(ans,p1-lv[x]);
	v[x].clear();
	return p1;
}
void dfs(int x,int f,int deep)
{
	dfsn[x]=cnt++;
	fa[x]=f;
	lv[x]=deep;
	size[x]=1;
	for(int i=0;i<v[x].size();i++)
		if(v[x][i]!=f)
		{
			int to=v[x][i];
			dfs(to,x,deep+1);
			size[x]+=size[to];
			if(size[son[x]]<size[to])
				son[x]=to;
		}	
}
void dfs2(int x,int p)
{
	top[x]=p;
	if(son[x])dfs2(son[x],p);
	for(int i=0;i<v[x].size();i++)
	{
		int to=v[x][i];
		if(lv[to]>lv[x]&&to!=son[x])
			dfs2(to,to);		
	}

}
int lca(int x,int y)
{	
	while(top[x]!=top[y])
	{
		if(lv[top[x]]<lv[top[y]])
			swap(x,y);
		x=fa[top[x]];
	}
	return lv[x]>lv[y]?y:x;
}
int cmp(int x,int y){
	return dfsn[x]<dfsn[y]; 
} 
int main(){
	int n;
	cin>>n;
	for(int i=1;i<n;i++)
	{
		int x,y,z;
		scanf("%d%d",&x,&y);
		v[x].push_back(y);
		v[y].push_back(x);
	} 
	cnt=1;
	//以点1为根建树 
	dfs(1,-1,0);
	dfs2(1,1);
	for(int i=1;i<=n;i++)
		v[i].clear();
	int q;
	cin>>q;
	while(q--)
	{
		int k,x=-1;
		scanf("%d",&k);
		for(int i=0;i<k;i++)
		{
			scanf("%d",&a[i]);
			vis[a[i]]=1;
		}
		sort(a,a+k,cmp);
		int index=0;
		st[0]=1;
		for(int i=0;i<k;i++)
		{
			if(a[i]==1)continue;//1一开始就已经入栈 
			int tp=st[index];
			int lca_=lca(tp,a[i]);
			if(lca_==tp)
			{
				st[++index]=a[i];
			}
			else
			{
				while(dfsn[st[index-1]]>dfsn[lca_])
				{
					v[st[index-1]].push_back(st[index]);
					index--;
				}		
				tp=st[index];
				if(dfsn[st[index-1]]<dfsn[lca_])
				{
					v[lca_].push_back(tp);
					st[index]=lca_;
					st[++index]=a[i];
				}
				else if(dfsn[st[index-1]]==dfsn[lca_])
				{
					v[lca_].push_back(tp);
					st[index]=a[i];
				}				
			}
		}
		while(index)
		{
			v[st[index-1]].push_back(st[index]);
			index--;
		}
		ans=0;
		dp(1);
		printf("%d\n",(ans+1)/2);
		for(int i=0;i<k;i++)
		{
			v[a[i]].clear();
			vis[a[i]]=0;
		}
	}
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值