ARC141F Well-defined Abbreviation

题意:有 $n$ 个串只包含 A, B, C, D 的 $S_i$,问是否存在一个 $T$,经过删除 $T$ 中存在的 $S_i$ 直到不存在任意一个 $S_i$ 后,可以得到不同的串。$n, \sum |S_i|\leq 2\times 10 ^ 6$,8s。

神仙 AC 自动机 + Hash 随机化分析。

首先分析几个结论:

结论 1:如果一个 $S_i$ 被其他串组合后完全删除,那么这个串是没有意义的。

证明:显然如果要删这个串的话,可以删除组成他的所有串。

结论 2:如果一个 $S_i$ 被其他串所删除但是没有完全删除,那么答案一定是 Yes。

证明:直接取 $T = S_i$,至少会得到空串和没有完全删除的串,已经符合题目条件。

有了上面两个结论之后,我们可以通过使用 AC 自动机暴力匹配,用一个栈记录匹配过程,如果匹配到了一个串的话,直接倒回到还没有加入串前的状态。这样一定不会出现贪心匹配没有完全匹配的情况(即有其他方案使得该串被完全删除),因为一旦出现这种情况,答案就是 Yes。

下面所有的串一定不会出现一个是另一个子串的问题了。

再来一个结论:

结论 3:如果出现两个串 $S_i$ 和 $S_j$($i, j$ 可能相同),$S_i$ 的某个后缀和 $S_j$ 的某个前缀是相同的,并且剩余部分不同,那么答案一定是 Yes。

证明:构造一个串为 $S_i$ 去掉相同后缀的前缀 + 共同后缀 / 前缀 + $S_j$ 去掉相同前缀的后缀。我们前面说到,不会再出现一个串是另一个子串的情况。那么 $S_i, S_j$ 分别删除后,剩下的两个串不同且无法消去,于是答案就是 Yes。

最后一个结论:

结论 4:不满足结论 3 的条件,答案一定是 No。

因为如果一个答案是 Yes 的话,一定出现了两个串相交的情形(否则没有相干,自己删自己的),而又不会出现子串的情形,所以肯定只有结论 3 的情况才会出现 Yes 的可能性。

下面就是需要判断 $S_i$ 的后缀和某个 $S_j$ 的前缀匹配,并且 $S_i, S_j$ 的剩余部分不同。有接着使用 AC 自动机做法,但人懒了,直接大力 Hash 做。

具体的,可以考虑枚举每一个后缀,将其 Hash 值和剩余部分的 Hash 值存下来,然后再枚举每个前缀,尝试匹配一些后缀。时间复杂度 $O(\sum |S|)$ 或 $O(\sum |S|\log \sum |S|)$。

最后一个问题就是众所周知的 Hash 冲突问题。其实一般想卡 Hash 是比较难的,因为 Hash 自选模数,自选 base,很不好卡,不同的字符串相当于是随机数对撞。注意到如果选取模数为 $P$ 的话,那么存的个数不能超过 $O(\sqrt P)$ 个,反过来,如果我们存了 $\sum |S|$ 个数,为了使不同的字符串分开,我们至少需要 $O((\sum|S|) ^ 2)$ 的 $P$,在本题中是 $10 ^ {13}$,所以一般的模数例如 998244353,$10 ^ 9 + 7$ 之类的会 WA 一些点。

写起来还是有亿点点难写,注意由于没有多测,不要看到过了一些点就觉得自己只是 corner case 没考虑到。WA 了 16 发才过……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
const int N = 2e6 + 10;
const LL Mod = LL(1e15) + 37;
struct Node {
int ch[4], fa;
} tr[N];
int n, tot, len[N], edid[N];
LL pw[N], hash[N];
bool hav[N];
std::string s[N];
std::map<LL, std::vector<PII>> mp[N];

template <class T>
inline bool chkmax(T &x, T y) { return x < y ? x = y, 1 : 0; }

void insert(const char *s, int id)
{
int u = 0;
for (int i = 0, c; s[i]; ++ i)
{
if (!tr[u].ch[c = s[i] - 'A'])
len[tr[u].ch[c] = ++ tot] = i + 1;
u = tr[u].ch[c];
}
edid[u] = id;
}

void build()
{
std::queue<int> q;
for (int i = 0; i < 4; ++ i)
if (tr[0].ch[i]) q.push(tr[0].ch[i]);
while (!q.empty())
{
int x = q.front();
q.pop();
chkmax(edid[x], edid[tr[x].fa]);
for (int i = 0; i < 4; ++ i)
if (!tr[x].ch[i]) tr[x].ch[i] = tr[tr[x].fa].ch[i];
else tr[tr[x].ch[i]].fa = tr[tr[x].fa].ch[i], q.push(tr[x].ch[i]);
}
}

void fail() { std::cout << "Yes\n", exit(0); }

void init()
{
pw[0] = 1;
for (int i = 1; i < N; ++ i) pw[i] = pw[i - 1] * 5 % Mod;
}

LL gethash(int l, int r)
{
if (l > r) return 0;
return ((r < 0 ? 0 : hash[r]) + (s128) (Mod - pw[r - l + 1]) * (l == 0 ? 0 : hash[l - 1])) % Mod;
}

void inithash(std::string s)
{
hash[0] = s[0] - 'A' + 1;
for (int i = 1; s[i]; ++ i) hash[i] = (hash[i - 1] * 5LL + s[i] - 'A' + 1) % Mod;
hash[s.size()] = 0;
}

signed main()
{
init(), std::cin.tie(0)->sync_with_stdio(false);
std::cin >> n;
for (int i = 1; i <= n; ++ i) std::cin >> s[i];
for (int i = 1; i <= n; ++ i) insert(s[i].c_str(), i);
build();
for (int id = 1; id <= n; ++ id)
{
static int stk[N];
int top = 0, bac = 0;
for (int i = 0, u = 0, c, mat; s[id][i]; ++ i)
{
c = s[id][i] - 'A', u = tr[u].ch[c];
stk[++ top] = u, mat = edid[u];
if (mat == id) mat = edid[tr[u].fa];
if (mat) bac = 1, u = stk[top -= s[mat].size()];
}
if (!top) continue;
if (bac) fail();
hav[id] = true, inithash(s[id]);
for (int i = 0; i < (int) s[id].size(); ++ i)
mp[s[id].size() - i][gethash(i, s[id].size() - 1)].push_back({gethash(0, i - 1), i});
}

for (int id = 1; id <= n; ++ id)
{
if (!hav[id]) continue;
inithash(s[id]);
for (int i = 1; i <= (int) s[id].size(); ++ i)
{
if (!mp[i].count(gethash(0, i - 1))) continue;
bool flag = true;
LL curhash = gethash(i, (int) s[id].size() - 1);
int curlen = s[id].size() - i;
for (auto [nxthash, nxtlen] : mp[i][gethash(0, i - 1)])
if (curhash != nxthash || nxtlen != curlen) flag = false;
if (!flag) fail();
}
}
std::cout << "No\n";
return 0;
}