前言
最近字符串模拟赛中遇到了一道诡异的题,std 的方法是启发式合并 Trie 树,但我写了个更简单(?)的 BFS+Trie树+LCA 做法。
后来得知是 CF 的题,就上来交一波,结果确实 AC 了。
这做法大概是对的,但是似乎有点慢(还难写)…
题意
有一棵以结点 $1$ 为根的树,每个结点上都有一个小写字母,对于以每个结点,从该结点出发向其子树中走,路上经过的点能形成一个字符串。求以每个结点为根的子树中能用此方法走出的不同的字符串数量。
CF上原题还有一些奇怪的操作就不说了,对上述答案做一下简单的处理就行了。
Solution
显然这道题和 Trie 树有关。
对每个点 $i$ 记录一个 $mus[i]$,表示以 $i$ 为根的子树中有多少个重复的字符串。(mus的意思大概是minus,即“减”)
考虑 BFS 原树,将遍历到的点插入到 Trie 树中,BFS 时候记录一下 Trie 树上每个点对应的在原树上的点数组 $trie$,如果插入的时候发现这个字符串已经被插入到 Trie 树中,说明有一个重复的字符串,将这个点和 Trie 树上的点对应在原树上点的 LCA 的 $mus$ 数组 $+1$,更新上面的 $trie$ 数组。
最后跑一遍 DFS 统计答案即可。
值得一提的是可以发现如果字符串相同,那么他们在原树上深度必定相同,所以所以求 LCA 的时候不用跳到深度相同了。
至于为什么在 LCA 上打标记,大概是因为打标记的时候要走到两个结点相同的,最早出现有至少两个儿子的字符相同的结点,在这里打标记,才会不重不漏。可以画画图理解。
时间复杂度 $O(n\log n)$,足以通过本题。
Code
我这代码常数贼大,别人最慢也跑 $30s$,我直接干到 $50s$。
还好这题时限开得也大(
#include <bits/stdc++.h>
using namespace std;
const int maxn = 300010;
int ch[maxn][28], tot = 1, trie[maxn]; // ch 是 Trie 树
// trie[i] 代表在 Trie 树上编号为 i 的结点对应在原树上的结点编号。
vector<int> e[maxn];
void add(int x, int y) { e[x].push_back(y), e[y].push_back(x); }
string s;
int mus[maxn], siz[maxn], ans[maxn], v[maxn], a[maxn], n;
int f[maxn][32];
struct node // BFS 用结构体
{
int fa, x, tfa; // fa,x 代表在原树中该结点父亲、该结点编号。
// tfa 代表在 Trie 树上该结点的父亲
};
void dfs_lca(int x, int fa)
{
f[x][0] = fa;
for (int i = 1; i <= 31; i++)
f[x][i] = f[f[x][i - 1]][i - 1];
for (int i = 0; i < e[x].size(); i++)
if (e[x][i] != fa)
dfs_lca(e[x][i], x);
}
int LCA(int x, int y)
{
if (x == y)
return x;
for (int i = 31; i >= 0; i--)
if (f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
return f[x][0];
}
void init()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
cin >> a[i];
cin >> s;
s = " " + s;
int x, y;
for (int i = 0; i < n - 1; i++)
scanf("%d%d", &x, &y), add(x, y);
dfs_lca(1, 0);
}
void bfs()
{
queue<node> q;
q.push((node){0, 1, 1});
while (!q.empty())
{
node now = q.front();
q.pop();
//now.P();
if (v[now.x])
continue;
v[now.x] = 1;
int c = s[now.x] - 'a';
if (ch[now.tfa][c]) // 如果当前结点对应字符串已经被插入到 Trie 树中
mus[LCA(now.x, trie[ch[now.tfa][c]])]++, trie[ch[now.tfa][c]] = now.x;
// 在两点 LCA 上打标记,并将 Trie 树上该点对应原树上结点更新
else
ch[now.tfa][c] = ++tot, trie[ch[now.tfa][c]] = now.x;
// 没被插入就新开个点,并更新 trie 数组
for (int i = 0; i < e[now.x].size(); i++)
q.push((node){now.x, e[now.x][i], ch[now.tfa][c]});
}
}
void dfs(int x, int fa) // DFS 统计答案
{
siz[x] = 1;
for (int i = 0; i < e[x].size(); i++)
{
if (e[x][i] != fa)
{
dfs(e[x][i], x);
siz[x] += siz[e[x][i]];
mus[x] += mus[e[x][i]];
}
}
ans[x] = siz[x] - mus[x];
}
void out_put()
{
int mx = -1, cnt = 0;
for (int i = 1; i <= n; i++)
{
if (mx == ans[i] + a[i])
cnt++;
else if (ans[i] + a[i] > mx)
mx = ans[i] + a[i], cnt = 1;
}
cout << mx << endl
<< cnt << endl;
}
void solve()
{
bfs();
memset(v, 0, sizeof(v));
dfs(1, 0);
}
int main()
{
init();
solve();
out_put();
return 0;
}