fyc

fyc ⁡ \operatorname{fyc} fyc

题目链接: luogu T145119 ⁡ \operatorname{luogu\ T145119} luogu T145119 / SSL比赛 1512 ⁡ \operatorname{SSL比赛\ 1512} SSL 1512

题目

fyc_第1张图片

输入

在这里插入图片描述

输出

在这里插入图片描述

样例输入

8 5
7 2 5 4 8
1 2 1
2 3 2
1 4 1
4 5 2
1 6 1
6 7 8
6 8 10

样例输出

5 1

数据范围

fyc_第2张图片

思路

这道题是一道图论题,用的算法不难,但是还是很恐怖。

我们先用两个 d f s dfs dfs 求出这个图的直径,然后找到中点。(如果中点在边上,那两边的点都可以用)
我们可以发现个东西,就是每个点的最远点都会经过这个中点。

那我们找到中点,以他为根跑一遍图,找到每个子树的最深深度,接着我们就来看分类讨论:

  1. 炸的地方位于深度最大的子树,且这种深度的子树只有一个:(如下图)fyc_第3张图片
    那没有影响的就是这个点上面的点(不包括根节点):
    fyc_第4张图片
  2. 炸的地方位于深度最大的子树,而且这种深度的子树有两个:(像这样)
    fyc_第5张图片
    那这次没有被影响的就也有这个点上面的点,而且还包括了根节点(因为根节点可以跑到另外一边去找另外的朋友),那就是这样:
    fyc_第6张图片
  3. 炸的地方位于深度第二大的子树,而且深度第二大的子树和深度最大的子树都只有一个,那就是这样:
    fyc_第7张图片
    那不会被影响的就是不在这两个子树的猪:
    fyc_第8张图片
    那就按这样分类讨论,就可以得出答案了。

代码

#include

using namespace std;

struct node {
	int x, to, nxt;
}e[200001];
int n, m, x, y, z, le[100001], KK;
int root, dep[100001], fa[100001], secroot[100001];
int maxd[100001], tot[100001], num[100001];
bool pig[100001];

void add(int x, int y, int z) {
	e[++KK] = (node){z, y, le[x]}; le[x] = KK;
	e[++KK] = (node){z, x, le[y]}; le[y] = KK;
}

void dfs(int now) {//通过dfs来找到直径
	if (pig[now] && dep[now] > dep[root])
		root = now;//找到离根距离最长的点
	for (int i = le[now]; i; i = e[i].nxt)
		if (fa[now] != e[i].to) {
			dep[e[i].to] = dep[now] + e[i].x;
			fa[e[i].to] = now;
			dfs(e[i].to);
		}
}

void dfs1(int now) {//以中点为根记录数据
	if (pig[now]) {//记录猪的位置
		maxd[now] = dep[now];
		tot[now] = 1;
		num[now] = 1;
	}
	for (int i = le[now]; i; i = e[i].nxt)
		if (fa[now] != e[i].to) {
			dep[e[i].to] = dep[now] + e[i].x;
			fa[e[i].to] = now;
			if (now == root) secroot[e[i].to] = e[i].to;//记录
				else secroot[e[i].to] = secroot[now];
			dfs1(e[i].to);
			tot[now] += tot[e[i].to];//记录猪数量
			if (maxd[now] < maxd[e[i].to]) {
				maxd[now] = maxd[e[i].to];
				num[now] = 0;
			}
			else if (maxd[now] == maxd[e[i].to]) num[now] += num[e[i].to]; 
		}
}

int main() {
	scanf("%d %d", &n, &m);//读入
	for (int i = 1; i <= m; i++) {
		scanf("%d", &x);
		pig[x] = 1;//记录猪的位置
	}
	for (int i = 1; i < n; i++) {
		scanf("%d %d %d", &x, &y, &z);//读入
		add(x, y, z);//建图
	}
	
	dfs(1);//找到直径
	dep[root] = 0;
	fa[root] = 0;
	dfs(root);
	
	int mid = dep[root] / 2;//找到中点
	while (dep[root] > mid)
		root = fa[root];
	
	dep[root] = 0;//以中点为根遍历图
	fa[root] = 0;
	dfs1(root);
	
	int maxdep[2] = {0}, maxnum[2] = {0}, one, two;
	for (int i = le[root]; i; i = e[i].nxt) {//找到深度最大和第二大的几个子树
		if (maxd[e[i].to] > maxdep[0]) {
			maxdep[1] = maxdep[0];
			maxnum[1] = maxnum[0];
			maxdep[0] = maxd[e[i].to];
			maxnum[0] = 1;
			one = e[i].to;
		}
		else if (maxd[e[i].to] == maxdep[0]) {
			maxnum[0]++;
			two = e[i].to;
		}
		else if (maxd[e[i].to] > maxdep[1]) {
			maxdep[1] = maxd[e[i].to];
			maxnum[1] = 1;
		}
		else if (maxd[e[i].to] == maxdep[1]) maxnum[1]++;
	}
	
	int ans = 0, ans1 = 0;
	for (int i = 1; i <= n; i++)
		if (!pig[i]) {//找到每个鞥呢炸的点
			int dead = tot[i];
			if (maxd[i] == maxd[secroot[i]]) {
				//第一种情况:位于深度最大的子树,且只有一个
				if (maxd[i] == maxdep[0] && maxnum[0] == 1) dead += tot[root] - tot[secroot[i]];
				//第二种情况:位于深度最大的子树,且有两个
				if (maxd[i] == maxdep[0] && maxnum[0] == 2) dead = tot[secroot[i]] + 1;
				//第三种情况:位于深度第二大的子树,而且有一个
				if (maxd[i] == maxdep[1] && maxnum[0] == 1 && maxnum[1] == 1) dead += tot[one];
			}
			if (dead > ans) {//更优,更新答案
				ans = dead;
				ans1 = 1;
			}
			else if (dead == ans) ans1++;//新的方法
		}
	
	printf("%d %d", ans, ans1);//输出
	
	return 0;
}

你可能感兴趣的:(#,图论,#,树,图论,分类讨论,树)