CodeForces - 856B Similar Words(AC自动机,树形dp)
题目大意
给你n个字符串,让你求一个集合,这些集合里的字符串是n个字符串中某些字符串的前缀,并且集合中的字符串任意两个都不相似,相似的定义为其中一个字符串去掉第一个字符和另一个完全相同。
解题思路
在字典树上从根到每一个节点的简单路径都是某些字符串的一个前缀,和他相似的点就是fail指针指向的比当前点深度小1的点(因为fail指针的定义是与当前字符串的后缀能匹配的最大前缀长度,深度小1自然就只有第一个不匹配),没有相似的字符串即当前节点i与fail指针指向的结点fail[i]只能选择一个,根据fail指针建出fail树跑树形dp即可。
代码
const int maxn = 2e6+10;
int tr[maxn][26], idx, dep[maxn];
char str[maxn];
vector<int> e[maxn];
void insert() {
int p = 0;
for (int i = 0; str[i]; ++i) {
int t = str[i]-'a';
if (!tr[p][t]) tr[p][t] = ++idx, dep[idx] = dep[p]+1;
p = tr[p][t];
}
}
int q[maxn], fail[maxn];
void build() {
int tt = -1, hh = 0;
for (int i = 0; i<26; ++i)
if (tr[0][i]) q[++tt] = tr[0][i];
while(hh<=tt) {
int t = q[hh++];
for (int i = 0; i<26; ++i) {
int p = tr[t][i];
if (!p) tr[t][i] = tr[fail[t]][i];
else {
fail[p] = tr[fail[t]][i];
q[++tt] = p;
}
}
}
for (int i = 0; i<=idx; ++i) clr(tr[i], 0);
}
int dp[maxn][2];
void dfs(int u) {
//cout << u << endl;
dp[u][0] = 0, dp[u][1] = 1;
for (auto v : e[u]) {
dfs(v);
dp[u][0] += max(dp[v][0], dp[v][1]);
dp[u][1] += dp[v][0];
}
}
int main() {
IOS;
int __; cin >> __;
while(__--) {
idx = 0;
int n; cin >> n;
for (int i = 1; i<=n; ++i) {
cin >> str;
insert();
}
build();
for (int i = 1; i<=idx; ++i) {
if (dep[i]==dep[fail[i]]+1) e[fail[i]].push_back(i);
else e[0].push_back(i);
}
dfs(0);
for (int i = 0; i<=idx; ++i) e[i].clear(), dep[i] = 0, fail[i] = 0;
cout << dp[0][0] << endl;
}
return 0;
}