栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

AtCoder

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

AtCoder

思维模拟加数学
因为是最远点,故必定为直径。
	
我们知道树的直径并不为一。但是考虑一下,如果一棵树的直径中有奇数个结点,其实对于它来说其“中心结点”是
每一条直径都必须通过的。(证明靠脑子)

由上面那个想法我们可以类比出偶数结点的情况,那么其“中心”将会变成一条边,也就是说这条边两边的点是必被
直径通过的存在。

所以,当结点为偶数时,只需要分别计算两侧结点所领导的子树中有多少个结点的深度是直径的一半即可。

但是在结点数为奇数个时问题就会有转变,因为类似于“菊花图”这种的,其实它隐含了一个组合问题,是:组间
可以同时出现,而组内不能同时出现,且至少要出现两个结点,问有多少种选择点的集合的方式。
	我们可以考虑的是由于“组内互斥”(不能同时出现)所以对于一个大小为 x 的组,它可以选择的结点有 x+1 种
	(因为可以不选)那么在满足“组内互斥”前提下总的选择方案为 (x1+1)*(x2+1)*(x3+1)*…*(xm+1) 其中 m 是
	组数。
	但是在上述选择下我们并没有考虑“至少出现两个结点”的条件,我们计算的是出现零个、一个、两个……一直到出
	现 m 个的情况,那么我们只需要删除出现零个和一个的情况就可以了,其分别为 1 和 x1+x2+…+xm 种情况。
#include 
using namespace std;

#define ll long long 
#define _for(i, a, b) for(int i = (a); i <= (b); i ++)
inline void read(int &x, int &y) { scanf("%d%d", &x, &y); }
int mod = 998244353;

int n;

int head[200009], nex[400009], to[400009], tot;  /// 链式前向星建树 
void add(int x, int y) {
	nex[++ tot] = head[x]; head[x] = tot; to[tot] = y;
}
int qu[200009], vis[200009];  /// 模拟队列;辅助数组; 
int bfs(int p) {   
	int l = 0, r = 0, ans;
	qu[r ++] = p;  vis[p] = -1;
	while(l < r) {
		p = qu[l ++];
		for(int i = head[p], x; i; i = nex[i]) {
			if(vis[x = to[i]]) continue;
			vis[x] = p;  qu[r ++] = x;  /// vis 数组记录每个点的前缀 
		}
		ans = p;
	}
	return ans;
}
int tx[200009], tox;   
void bfsf(int x) {
	memset(vis, 0, sizeof vis);  /// 清空第一次计算的结果 
	x = bfs(x);  /// 利用第一次的 bfs 结果进行二次广搜 
	tx[++ tox] = x;
	while(vis[tx[tox]] != -1 && tox ++) tx[tox] = vis[tx[tox - 1]];  /// 根据前缀找出一条直径 
}
int bfs1(int x, int y) {  /// 从 x 位置广搜,返回距离 x 为 y-1 的结点的个数 
	int ans = 0;
	int l = 0, r = 0;
	qu[r ++] = x;
	while(l < r) {
		x = qu[l ++];  if(vis[x] == y) ans ++;  /// 统计结点个数 
		for(int i = head[x], p; i; i = nex[i]) {
			if(vis[p = to[i]]) continue;
			vis[p] = vis[x] + 1;  qu[r ++] = p;  /// 这里的 vis 数组用于记录结点于 x 的距离(严格来说是 距离+1 ) 
		}
	}
	return ans;
}
int main() {
	cin >> n;
	int x, y;
	_for(i, 1, n - 1) {
		read(x, y);  add(x, y);  add(y, x);
	}
	bfsf(bfs(1));   /// 计算直径路径 
	memset(vis, 0, sizeof vis);  /// 注意这里的初始化,后面对于 vis 数组是不会再初始化了的 
	if(tox & 1) {
		vis[tx[tox + 1 >> 1]] = 1;  /// 中心结点 
		ll sum1 = 1, sum2 = 0;
		for(int i = head[tx[tox + 1 >> 1]], p; i; i = nex[i]) {  /// 遍历中心结点周围的每一个结点 
			vis[p = to[i]] = 1;
			x = bfs1(p, tox >> 1);
			sum2 += x;  sum1 = sum1 * (x + 1) % mod;
		}
		cout << (sum1 - sum2 - 1 + mod) % mod;   /// 容斥原理 
	} else {
		vis[tx[tox >> 1]] = 1;  vis[tx[(tox >> 1) + 1]] = 1;
		cout << 1ll * bfs1(tx[tox >> 1], tox >> 1) * bfs1(tx[(tox >> 1) + 1], tox >> 1) % mod;   /// 由于中心边两边的结点必处在直径之中,故直接乘即可                        
	}
	return 0;
} 
涉及知识点:搜索,树的直径,链表,容斥原理。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/304172.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号