P9648 [SNCPC2019] Unrooted Trie 题解

题面

P9648,因测试数据过少且不支持下载,可前往我自己造的相应题目进行调试。

trie 的定义是这样的:

  • 一棵大小为 n n n 的 trie,是一棵有着 n n n 个节点和 ( n − 1 ) (n−1) (n1) 条边的有根树,每一条边上都标有一个字符;

  • 在 trie 中,从根结点到树上某一结点的路径代表一个字符串,节点 x x x 代表的字符串记为 s ( x ) s(x) s(x),特别地,根节点代表的字符串为空串。

  • 若节点 u u u 是节点 v v v 的父节点,且 c c c 是连接 u u u v v v 的边上的字符,则有 s ( v ) = s ( u ) + c s(v)=s(u)+c s(v)=s(u)+c(这里的 + + + 表示字符串的连接,而非普通的加法运算)。

当每一个节点代表的字符串互不相同时,该 trie 是合法的。
给出一个无根的 trie,求其中有多少节点可作为该 trie 的根,使得该 trie 合法。

思路

在这里插入图片描述

首先容易想到枚举根节点再依次判断,但是复杂度无法通过本题。考虑以下性质:

  • 对于一个以 i i i 号节点为根的树,如果存在 a , b , c a,b,c a,b,c 使得 b , c b,c b,c a a a 儿子,且从 a a a b b b 与从 a a a c c c上的字符相等,那么 i i i 为根的树非法,因为 s ( b ) = s ( c ) s(b) = s(c) s(b)=s(c) ( 1 ) (1) (1)
  • 对于一个节点,如果与其相连的节点中有 3 3 3 个满足边上的字符都相同,那么这个图中任意一个点做节点都是非法的,因为无论以谁为根节点都无法满足(见 ( 1 ) (1) (1))。
  • 如果存在 a , b , c a,b,c a,b,c 使得 a , b a,b a,b a , c a,c a,c 均联通,且从 a a a b b b 与从 a a a c c c上的字符相等,如果 i i i 节点到 a a a 节点的路径上不经过 b b b c c c 两点,则 i i i 节点为根的树非法,因为会造成 b , c b,c b,c 均为 a a a 儿子。

通过以上性质,我们可以从 1 1 1 号节点遍历,枚举判断每一个节点时候合法。

代码

AC 记录

#include<bits/stdc++.h>
using namespace std;
int T;
int n,head[200005],nex[400005],to[400005],cnt = 0;
char w[400005]; 
bool can[200005];
int num;
//清空 
void qk() {
	cnt = 0,num = n;
	for(int i = 0;i <= 200000;i++) head[i] = 0,can[i] = true;
	for(int i = 0;i <= 400000;i++) nex[i] = 0,to[i] = 0,w[i] = ' ';
	return;
}
//加边
void add(int x,int y,char z) {
	nex[++cnt] = head[x];
	head[x] = cnt;
	to[cnt] = y;
	w[cnt] = z;
} 
bool dfs(int now,int fa,bool ok) {
	//ok 代表当前点是否合法 
	int xt = 0;
	if(!ok) xt++;
	bool get = ok;
	if(num == 0) return false;
	bool return_ok = true,return_ok2 = true;
	int have[27][3];
	for(int i = 0;i <= 26;i++) have[i][1] = -1,have[i][2] = -1;
	// 找到与父亲连接的边对应的字符 
	for(int i = head[now];i;i = nex[i]) {
		if(to[i] == fa) {
			have[int(w[i] - 'a')][1] = fa;
			break;
		}
	}
	//找到剩下的边 
	for(int i = head[now];i;i = nex[i]) {
		if(to[i] == fa) continue;
		if(have[int(w[i] - 'a')][1] == -1) have[int(w[i] - 'a')][1] = to[i];
		else if (have[int(w[i] - 'a')][2] != -1) {
			num = 0;
			return false;
		}
		else {
			have[int(w[i] - 'a')][2] = to[i];
			if(!(have[int(w[i] - 'a')][1] == fa and !ok)) xt++;
			if(have[int(w[i] - 'a')][1] == fa) return_ok2 = true;
			else return_ok2 = false;
			if(xt == 2) {
				num = 0;
				return false;
			}
		}
	}
	if(xt) ok = false;
	for(int i = head[now];i;i = nex[i]) {
		if(to[i] == fa) continue;
		if(!return_ok) {
			dfs(to[i],now,return_ok);
			continue;
		}
		if(ok) {
			if(!dfs(to[i],now,ok)) {
				return_ok = false;
				for(int j = head[now];j != i;j = nex[j]) {
					if(to[j] != fa) dfs(to[j],now,return_ok);
				}
			}
		}
		else {
			if(have[int(w[i] - 'a')][2] == to[i]) {
				if(!dfs(to[i],now,get)) {
					return_ok = false;
					for(int j = head[now];j != i;j = nex[j]) {
						if(to[j] != fa) dfs(to[j],now,return_ok);
					}
				}
			}
			else if(have[int(w[i] - 'a')][1] == to[i] and have[int(w[i] - 'a')][2] != -1){
				if(!dfs(to[i],now,get)) {
					return_ok = false;
					for(int j = head[now];j != i;j = nex[j]) {
						if(to[j] != fa) dfs(to[j],now,return_ok);
					}
				}
			}
			else if(!dfs(to[i],now,ok)) {
				return_ok = false;
				for(int j = head[now];j != i;j = nex[j]) {
					if(to[j] != fa) dfs(to[j],now,return_ok);
				}
			}
		}
		if(num == 0) return false;
	}
	if(!ok or !return_ok or !return_ok2) {
		if(num == 0) return false;
		if(can[now]) can[now] = false,num--;
	}
	return (return_ok && return_ok2);
}
signed main() {
	scanf("%d",&T);
	while(T--) {
		scanf("%d",&n);
		qk();
		for(int i = 1;i < n;i++) {
			int x,y;
			char z;
			scanf("%d %d",&x,&y);
			cin >> z;
			add(x,y,z),add(y,x,z);
		}
		dfs(1,1,1);
		printf("%d\n",num);
	}
    return 0;
}
  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值