Candy选首都【DFS】【树】

>Description
Treeland是一个有n个城市组成的国家,其中一些城市之间有单向边连通。在这个国家中一共有n-1条路。我们知道,如果我们不考虑路的方向,那么我可以从任意城市到达任意城市。
最近,Treeland的总理Candy为了发展经济,想要从这n个城市中选择一个作为Treeland的首都,首都必须要能到达其他任意城市,这使得有些道路必须反向,付出的代价即需要反向的道路条数。
Candy想要选择一个城市作为首都,使得付出的代价最小。可能有多个城市满足条件,按编号从小到大输出。


>Input
第一行,一个整数n,表示城市个数
接下来n-1行,每行两个整数x、y,表示城市x到城市y之间有一条单向路径

>Output
第一行,一个整数k,花费的最小代价。
第二行若干个整数,中间用空格隔开,表示满足条件的城市编号。行末没有多余的空格。


>Sample Input
Sample Input1:
3
2 1
2 3

Sample Input2:
4
1 4
2 4
3 4

>Sample Output
Sample Output1:
0
2

Sample Output2:
2
1 2 3

对于70%的数据 n<=5000
对于100%的数据 n<=2*10^5


>解题思路
如果不看题目中边的方向,路线就强制组成了一棵树。

把1(随便找个数)作为根,计算每个点到它子树中的所有节点的代价,存进s数组
然后再用一个dfs计算答案,搜到每一个点计算答案时,就会发现:除了它经过的边,到达其他点的路线(代价)都与 s [ 1 ] s[1] s[1]相同,所以再用一个很变态的东西处理一下经过的边就好了。


>代码

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

struct tree
{
	int to, next, c; //c标记这条边存不存在
} a[400005];
int n, x, y, t, ans, h[200005], s[200005], st[200005];
bool yd[200005];

void dfs (int now)
{
	yd[now] = 1;
	
	for (int i = h[now]; i; i = a[i].next)
	  if (!yd[a[i].to])
	  {
	  	dfs (a[i].to); //dfs儿子
	  	s[now] += s[a[i].to];
	  	if (a[i].c == 0) s[now]++; //如果这条边不存在的话代价+1
	  }
	  
	yd[now] = 0;
}
void ct (int cc, int now, int dep) //cc为经过的边有多少是由根顺着下来的,now为当前节点,dep为深度(经过的边数)
{
	int yes = dep - cc, no = cc; //yes为由当前节点到根有多少是顺着的,no为逆着的。下来是顺着的上去就变成了逆着的
	int sum = s[1] - yes + no; //因为顺着上去的在当时计算s时是逆着下来的,所以要减去,再加上逆着上去的
	if (sum < ans)
	{
		ans = sum;
		st[0] = 0;
		st[++st[0]] = now; //加入队列
	}
	else if (sum == ans)
	  st[++st[0]] = now; //累计答案
	yd[now] = 1;
	for (int i = h[now]; i; i = a[i].next)
	  if (!yd[a[i].to])
	    ct (cc + a[i].c, a[i].to, dep + 1);
	yd[now] = 0;
}
int main()
{
	scanf ("%d", &n);
	for (int i = 1; i < n; i++)
	{
		scanf ("%d%d", &x, &y);
		a[++t] = (tree) {y, h[x], 1}; h[x] = t;
		a[++t] = (tree) {x, h[y], 0}; h[y] = t; //强制
	}
	dfs (1); //计算s
	
	ans = 200000;
	ct (0, 1, 0);
	
	printf ("%d\n", ans);
	sort (st + 1, st + 1 + st[0]);
	for (int i = 1; i <= st[0]; i++)
	 printf ("%d ", st[i]);
	return 0;                   
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值