牛客月赛8-病毒感染-(树的重心+性质)

A

题意:
就是给你一个图,然后这个图呢所有边权都是1,图的类型保证没有大小大于等于3的环。然后小A不知道自己在哪个点了,他只知道,自己所在的点到其余所有点的最短路径的和是最小的。现在问你小A在哪些点,请全部输出。

思考:
看到题目感觉应该是用到树上的啥性质了,如果用最短路的话,一般这种题做不了。然后看了题解发现,是用了树的重心这个性质,树的重心到其余所有点的权值和最小。然后求树的重心就行了,这里我们就要利用这个每个边都是1的特性了。我们采用dfs维护距离和的方法,假设当前节点是sum[x],我们要转移到sum[y],就有这么一条转移方程 sum[y]=sum[x]-cnt[y]+(n-cnt[y]).这个cnt[y]代表的是y这个节点的子树重量。从x到y这一步,y这个节点到y的子节点的距离全部都-1 故-cnt[y],同时不是子树的点,都+1,所以+(n-cnt[y])。那到了这一步,我们需要求出cnt数组了。我们假设刚开始以1为根,然后dfs回溯的时候算出cnt。最后再来一次dfs维护距离和,求出sum数组。问题就得到解决。
这里是一张树的重心的性质的图:在这里插入图片描述
同时呢,以前做过一道用树的直径性质的题目:巨木之森。这个题用到的性质就是,树上的点距离他最远的点就是他到这颗树的直径的两个端点的最大值。

代码:

直接维护出点到其他点最小值的做法:

int T,n,m,k;
int minn = inf;
int va[N];
int dep[N],cnt[N],sum[N];

vector<int > e[N];

void get(int now,int p)
{
	dep[now] = dep[p]+1;
	cnt[now] = 1;
	for(auto spot:e[now])
	{
		if(spot==p) continue;
		get(spot,now);
		cnt[now] += cnt[spot];
	}	
}

void dfs(int now,int p)
{
	for(auto spot:e[now])
	{
		if(spot==p) continue;
		sum[spot] = sum[now]-cnt[spot]+(n-cnt[spot]);
		dfs(spot,now);
	}
	minn = min(minn,sum[now]);
}

signed main()
{
	IOS;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		e[a].pb(b);
		e[b].pb(a);
	}
	get(1,0);
	for(int i=1;i<=n;i++) sum[1] += dep[i];
	dfs(1,0);
	for(int i=1;i<=n;i++)
	{
		if(sum[i]==minn)
		cout<<i<<" ";
	}
	return 0;
}

树上get一遍求出重心,然后再从重心跑一遍spfa求出最小的总和:

int T,n,m,k;
int zx,maxn = inf;
int dep[N],cnt[N],siz[N];
int dist[N],vis[N];

vector<int > e[N];

void get(int now,int p)
{
	dep[now] = dep[p]+1;
	cnt[now] = 1;
	for(auto spot:e[now])
	{
		if(spot==p) continue;
		get(spot,now);
		cnt[now] += cnt[spot];
		siz[now] = max(siz[now],cnt[spot]);
	}
	siz[now] = max(siz[now],n-cnt[now]);
	if(maxn>siz[now]) 
	{
		zx = now;
		maxn = siz[now];
	}
}

void spfa(int x)
{
	for(int i=1;i<=n;i++) dist[i] = inf;
	queue<int > q;
	q.push(x);
	dist[x] = 0;
	vis[x] = 1;
	while(q.size())
	{
		auto now = q.front();
		q.pop();
		vis[now] = 0;
		for(auto spot:e[now])
		{
			if(dist[spot]>dist[now]+1)
			{
				dist[spot] = dist[now]+1;
				if(!vis[spot])
				{
					vis[spot] = 1;
					q.push(spot);
 				}
 			}
		}
	}
}

signed main()
{
	IOS;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		e[a].pb(b);
		e[b].pb(a);
	}
	get(1,0);
	spfa(zx);
	int ans = 0;
	for(int i=1;i<=n;i++) ans += dist[i];
	cout<<ans<<"\n";
	return 0;
}

总结:
多多积累经验。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值