题面
P9648,因测试数据过少且不支持下载,可前往我自己造的相应题目进行调试。
trie 的定义是这样的:
-
一棵大小为 n n n 的 trie,是一棵有着 n n n 个节点和 ( n − 1 ) (n−1) (n−1) 条边的有根树,每一条边上都标有一个字符;
-
在 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 号节点遍历,枚举判断每一个节点时候合法。
代码
#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;
}