CF763D·Timofey and a flat tree

初见安~这里是传送门:CF763 D

题意:一棵无根树,找一个点做为根节点使互不同构的子树数量最多。输出任意解。

两种思路:找一锤定音的性质和暴力判定不同构的子树数量。

前者显然不可取,我们考虑后者。

判断两棵子树是否相同,可以用树hash来判定。

这里选用的式子是如下:

hash[u]=size[u]*(size[u]+\sum_{v \in son_u}hash[v])

大概其他的式子也可以用吧【小声

于是我们可以以1为根节点,算出每个子树的hash值。然后扔进map里计数,顺便去重。

接下来考虑找扔进map后使map的size最大的那个点。

可以想到换根,但是换根后的子树全都会变。或者说,从一个点往另一个点的这条边反向

比如,一开始是以1为根,边全部向下指向子树。

当我把根换到1的其中一个儿子后,该儿子到1的边反向,其余不变。

也就是说,每次换根只会在当前基础上改变一条边的方向,那么子树就只是改变了一棵。

在上述例子中,以1为大根的树没了,但是多了一棵以该儿子为大根的树。

所以——用一个map记录以当前节点为根时子树的hash值的信息,换根的时候修改map信息即可。

最后就是回到了hash值的运算。针对每条边的方向求2n个hash值即可。

上代码——

#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
#include<map>
#define maxn 200005
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int inf = 2e5;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

struct edge {int to, nxt;} e[maxn];
int head[maxn], k = 0;
void add(int u, int v) {e[k] = {v, head[u]}; head[u] = k++;}

int n, ans1, ans2, tot = 0;
map<int, int> mp;
void ist(int x) {if(!mp[x]) ++tot; ++mp[x];}
void dlt(int x) {--mp[x]; if(!mp[x]) --tot;}

int hs[maxn], size[maxn];
void dfs1(int u, int fa, int eg) {//初始eg=inf是因为第一个节点没有对应的边,但也有自己的hash值。
	size[eg] = 1;
	for(int i = head[u], v; ~i; i = e[i].nxt) {
		v = e[i].to; if(v == fa) continue;
		dfs1(v, u, i);
		hs[eg] = (1ll * hs[eg] + hs[i]) % mod;
		size[eg] += size[i];
	}
	hs[eg] = (1ll * hs[eg] * size[eg] % mod + 1ll * size[eg] * size[eg] % mod) % mod;
	ist(hs[eg]);
}

void dfs2(int u, int fa) {
	register int sum = 0;
	for(int i = head[u], v; ~i; i = e[i].nxt) sum = (1ll * sum + hs[i]) % mod;
	for(int i = head[u], v; ~i; i = e[i].nxt) {
		v = e[i].to;
		size[i ^ 1] = n - size[i];
		hs[i ^ 1] = (1ll * size[i ^ 1] * (sum - hs[i] + mod) % mod + 1ll * size[i ^ 1] * size[i ^ 1]) % mod;
		if(v != fa) dfs2(v, u);
	}
}

void slv(int u, int fa) {
	for(int i = head[u], v; ~i; i = e[i].nxt) {
		v = e[i].to; if(v == fa) continue;
		dlt(hs[i]), ist(hs[i ^ 1]);
		if(tot > ans1) ans1 = tot, ans2 = v;
		slv(v, u);
		dlt(hs[i ^ 1]), ist(hs[i]);
	}
}

signed main() {
	n = read(); memset(head, -1, sizeof head);
	for(int i = 1, u, v; i < n; ++i) u = read(), v = read(), add(u, v), add(v, u);
	dfs1(1, 0, inf); dfs2(1, 0);//因为本来双向边就是两个单向边,所以每条边都算一个hash值。
	ans1 = tot, ans2 = 1; 
	slv(1, 0);
	printf("%d\n", ans2);
	return 0;
}

应该还挺好理解的吧。

迎评:)
——End——

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值