UOJ #11.【UTR #1】ydc的大树 题解

题目传送门

题目大意: 给一棵由黑白点组成的树,如果一个黑点无法到达所有离他最远的点,他就会不开心,问有多少种方式使得删掉一个白点后不开心的黑点数最大。

题解

首先这题用到一个结论:树上任意一点到离他最远的点的路径一定经过树的中心(不是重心),那么对于这题,我们就可以先找到树的中心,将它定为树根,这样可以保证每条到最远点的路径都经过根。

我们都知道,找树的中心要先找树的直径,因为这题只有黑点要找最远的黑点,所以我们找直径时只看黑点,不看白点,具体代码是这样的:

//应该都知道找直径是要两次dfs找最远点的吧qwq,但还是有必要提一下
void dfs1(int x,int fa,int *dis)//dis数组记录每个点到出发点的距离
{
	if(dis[x]>dis[furt])furt=x;//furt即furthest,记录离出发点最远的黑点
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y; if(y==fa)continue;
		dis[y]=dis[x]+e[i].z; dfs1(y,x,dis);
	}
}

主函数中就是这么一小段:

dfs1(1,0,a); a[furt]=0; dfs1(furt,0,a);

接下来枚举每一个白点,统计将它删除后有多少个不高兴的黑点。以及为了方便,这里做个定义:如果一个点 x x x 在根的儿子 y y y 的子树内,那么设 t o p [ x ] = y top[x]=y top[x]=y

由于我们将中心定成了根,那么对于任意一个黑点 x x x,离他最远的黑点就是不在 t o p [ x ] top[x] top[x] 的子树内的离根最远的黑点,于是我们需要统计一下,根的每个儿子的子树内离根最远的黑点到根的距离以及数量。

另外,我们还需要统计出所有黑点中离根最远的是在哪个儿子的子树中,最远的记为 m a 1 ma1 ma1,次远的记作 m a 2 ma2 ma2,有可能有两个儿子都含有最远黑点,但如果有三个或以上的儿子含有最远黑点,就会发现,删掉任意一个白点后,只有这个白点内的黑点会不高兴,一定不会影响到其它黑点。

以及,删去一个白点 x x x 后,在 t o p [ x ] top[x] top[x] 的子树内的且不在 x x x 子树内的黑点一定不受影响。

接下来就是分类讨论了,分类讨论这部分配合代码食用更佳:

for(int i=1;i<=n;i++)//枚举白点
{
	if(col[i]||i==root)continue;
	int ans_=black[i];
	//black[i]表示i的子树内黑点的个数,显然,将i删去后它子树内的黑点一定都会不高兴
	//下面就是统计子树外的黑点有多少个会不高兴,不高兴的前提是那个黑点的所有最远点都在i的子树内
	if(black[i]&&a[i]==a[top[i]]&&b[i]==b[top[i]])
	//对black[i]的判断大家都懂,后面判断的是在top[i]的子树内离根最远的黑点是否都在i的子树内
	//如果不是,那么删去i后对其它的黑点肯定都没有影响,这个想想就能明白
	{
		if(top[i]==ma1)//假如top[i]的子树内有最远黑点
		{
			if(a[ma1]==a[ma2])ans_+=black[ma2];
			//假如最远和次远一样远,那么删去i后只有次远的整棵子树会到不了离他们最远的黑点
			else ans_+=m-black[ma1];//假如最远比次远要远,那么除了最远的这棵子树,其他点都会受影响
		}
		else if(top[i]==ma2)ans_+=black[ma1];//如果是次远,那么只有最远会受影响
	}
	if(ans_>ans)ans=ans_,anss=1;//更新答案
	else if(ans_==ans)anss++;
}

完整代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100010

int n,m,col[maxn];
struct edge{int y,z,next;};
edge e[maxn<<1];
int first[maxn],len=0;
void buildroad(int x,int y,int z)
{
	e[++len]=(edge){y,z,first[x]};
	first[x]=len;
}
int a[maxn],b[maxn],furt=1;
void dfs1(int x,int fa,int *dis)//找离出发点最远的点
{
	if(dis[x]>dis[furt])furt=x;
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y; if(y==fa)continue;
		dis[y]=dis[x]+e[i].z; dfs1(y,x,dis);
	}
}
int abs(int x){return x<0?-x:x;}
int black[maxn],top[maxn],root=0;
void dfs2(int x,int fa,int dis)//统计每个点子树内的黑点数以及最远的黑点
{
	if(fa==root)top[x]=x; else top[x]=top[fa];
	black[x]=col[x]; if(col[x])a[x]=dis,b[x]=1;
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y; if(y==fa)continue;
		dfs2(y,x,dis+e[i].z);
		if(a[y]>a[x])a[x]=a[y],b[x]=b[y];
		else if(a[y]==a[x])b[x]+=b[y];
		black[x]+=black[y];
	}
}

int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1,x;i<=m;i++)
	scanf("%d",&x),col[x]=1;
	for(int i=1,x,y,z;i<n;i++)
	scanf("%d %d %d",&x,&y,&z),buildroad(x,y,z),buildroad(y,x,z);
	dfs1(1,0,a);
	a[furt]=0; int p=furt;
	dfs1(furt,0,a); dfs1(furt,0,b);
	for(int i=1;i<=n;i++)
	{
		if(a[i]+b[i]==b[p])//假如该点在直径上
		{
			if(root==0)root=i;
			//并且这个点更靠近中点,就更新树的中心
			else if(abs(a[i]-b[i])<abs(a[root]-b[root]))root=i;
		}
	}
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	dfs2(root,0,0);
	int ma1=0,ma2=0;
	for(int i=first[root];i;i=e[i].next)
	{
		int y=e[i].y; if(!black[y])continue;
		if(a[y]>a[ma1])ma2=ma1,ma1=y;//找最远和次远
		else if(a[y]>a[ma2])ma2=y;
	}
	for(int i=first[root];i;i=e[i].next)//假如有三个一样的最远,那么一定不会影响子树外的点
	if(e[i].y!=ma1&&e[i].y!=ma2&&a[e[i].y]==a[ma1]){ma1=ma2=0;break;}
	int ans=0,anss=0;
	if(!col[root])ans=m,anss=1;//统计根的答案
	for(int i=1;i<=n;i++)
	{
		if(col[i]||i==root)continue;
		int ans_=black[i];
		if(black[i]&&a[i]==a[top[i]]&&b[i]==b[top[i]])
		{
			if(top[i]==ma1)
			{
				if(a[ma1]==a[ma2])ans_+=black[ma2];
				else ans_+=m-black[ma1];
			}
			else if(top[i]==ma2)ans_+=black[ma1];
		}
		if(ans_>ans)ans=ans_,anss=1;
		else if(ans_==ans)anss++;
	}
	printf("%d %d",ans,anss);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值