ARC141F Well Defined Abbreviation
一般的非法串不容易考虑,先从给出的这些串入手。从 拿来删其他串 的角度来说,那些能够被其他给出串删空的给出串是不必考虑的。我们先把这样的串删掉。请注意,这一步里删掉的串即使是非法串,在后文也会考虑到它。那么现在考虑如何判断这样的串:容易想到一个贪心,从前往后扫每一位,如果当前某个后缀匹配了那么删掉这个后缀,并继续扫。这个贪心明显是错的,hack 很好构造。但是在这题里,我们可以这样做。我们先不管为什么,把通过这个贪心判断出来能被删空的串全部删去,然后接着考虑。
接下来假定我们所有的串都是不能被其他串删空的,那么这个时候如果存在一个串 \(S\) 包含另一个串 \(T\) 作为子串,那么直接取 \(S\),就是一个非法串。因为显然 \(S\) 可以被 \(S\) 删空,但是 \(S\) 删了 \(T\) 之后不能被其他串删空(否则违背假定),因此有两种结果串,则此时存在非法串。
上文的贪心为什么在这题中能用?
上文的贪心为什么在这题中能用会发现如果我们错误的判断了一个串 \(S\) 不能被其他串删空(但实际上它可以),那么这个串在跑完贪心之后剩下的那个串 \(T\) 一定既不是 \(S\),也不是空串。但是 \(S\) 其实可以被 \(S\) 删空,这里就有了两种不同的结果串(空串和 \(T\)),因此是存在非法串的。而由于我们的贪心认为它不能被删空,放它进入了上一段那个步骤,那么它也会包含另一个串作为子串,那么判出来也是存在非法串。这两种的结果是一样的,因此我们直接跑这个贪心,不会有任何结果上的差别。因此在这个意义上,它是能用的。
那么现在的情况是所有给出串都不包含任何其他给出串作为子串。此时,我们考虑所有非法串中最短的那个。设其为 \(S\)。考虑若 \(S\) 中存在相交的两个匹配,设其中一个为 \(AB\),另一个为 \(BC\),那么分类讨论:
-
若存在 \(A \neq C\) 的情况,则若 \(S \neq ABC\),则 \(ABC\) 为更短的非法串,与 \(S\) 最短矛盾;
-
否则不存在,所有符合条件的情况都满足 \(A = C\),不妨直接称为 \(ABA\)。那么这个时候我们考虑所有 \(S\) 中的匹配区间,那么根据相交关系我们划分连通块。显然同一个连通块中所有东西长度相同,且按左端点升序后,对于任意两个相邻区间,删去一个的效果就等同于删去另一个的效果。那么这个时候我们考虑其中一个连通块中的东西,显然一个连通块中的东西是要被干涉至少一次的。而这一次干涉一定可以转化为删去连通块中的最后一个区间(其实任意都可以),那么这个时候我们直接在最开始就从 \(S\) 里删掉这个区间,容易发现这不会对非法性产生任何影响。于是 \(S\) 不是最短非法串。矛盾。
那么我们证明了除非 \(S = ABC\),其中 \(AB\) 和 \(BC\) 都存在于给出串中,\(S\) 中不会有相交的匹配。那么会不会有不交的匹配呢?显然在 \(S\) 中所有匹配都不交的情况下,只能有一种可行结果串。因此这样的 \(S\) 一定不非法。
于是我们证明了最短非法串必然满足 \(S = ABC\) 的形式,其中 \(AB\) 和 \(BC\) 都存在于给出串中。我们只需要判断给出串中是否存在这样的情况即可。我们枚举 \(B\),那么 \(AB\) 在 fail 树上 \(B\) 串代表节点的子树内,而 \(BC\) 在 trie 树上 \(B\) 串代表的子树内。我们只需要判断能否在这两个子树里分别找出两个串,使得他俩不等。那么显然如果两个子树中有一个里没串,则一定找不到;否则若其中一个有至少两个串,那么一定能找到。否则两个子树里都恰好只有一个串,只需要把这两个串拉出来判一下是否相等即可。容易哈希。于是就做完了,总时间复杂度线性。
代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#include <queue>
#define lowbit(x) ((x) & (-(x)))
using namespace std;
const int inf = 0x3f3f3f3f;
const __int128 B = 5551, P = 3355779988664422213;
inline void Cmin(int &x, int y) { x = min(x, y); }
int T[2000005][4];
int ed[2000005], fail[2000005], ncnt;
int New() {
++ncnt; ed[ncnt] = inf; fail[ncnt] = 0;
memset(T[ncnt], 0, sizeof T[ncnt]);
return ncnt;
}
void Clear() { ncnt = -1; New(); }
queue<int> q;
int Insert(string str) {
int p = 0;
for (auto v : str) {
int t = v - 'A';
if (!T[p][t]) T[p][t] = New();
p = T[p][t];
}
Cmin(ed[p], str.size());
return p;
}
void Build() {
for (int i = 0; i < 4; i++) {
if (T[0][i])
q.push(T[0][i]);
}
while (!q.empty()) {
int x = q.front();
q.pop();
Cmin(ed[x], ed[fail[x]]);
for (int i = 0; i < 4; i++) {
if (T[x][i])
q.push(T[x][i]), fail[T[x][i]] = T[fail[x]][i];
else
T[x][i] = T[fail[x]][i];
}
}
}
int c1[2000005], c2[2000005], len[2000005];
__int128 h1[2000005], h2[2000005], pw[2000005];
vector<int> vec[2000005];
bool fl[2000005][4];
void build() {
for (int i = 0; i < 4; i++) T[0][i] ? (fl[0][i] = 1, q.push(T[0][i])) : void();
while (!q.empty()) {
int x = q.front(); q.pop();
vec[fail[x]].emplace_back(x);
for (int i = 0; i < 4; i++) T[x][i] ? (fl[x][i] = 1, q.push(T[x][i]), fail[T[x][i]] = T[fail[x]][i]) : (T[x][i] = T[fail[x]][i]);
}
}
int n;
string str[1000005];
int stk[2000005], sz;
bool ok[1000005];
void dfs1(int x) {
for (int v : vec[x]) {
len[x] += len[v], len[v] = 0, c1[x] += c1[v], c1[v] = 0, h1[x] = (h1[x] + h1[v]) % P, h1[v] = 0;
dfs1(v);
len[x] += len[v], c1[x] += c1[v], h1[x] = (h1[x] + h1[v]) % P;
}
}
void dfs2(int x) {
for (int i = 0; i < 4; i++) {
int v = T[x][i];
if (fl[x][i]) {
c2[x] += c2[v], c2[v] = 0, h2[x] = (h2[x] + h2[v]) % P, h2[v] = 0;
dfs2(v);
c2[x] += c2[v], h2[x] = (h2[x] + h2[v]) % P;
}
}
}
bool okk;
void dfs3(int x, int d, __int128 h) {
if (x) {
if ((c1[x] > 1 && c2[x]) || (c2[x] > 1 && c1[x])) okk = 1;
else if (c1[x] == 1 && c2[x] == 1) {
__int128 a = h1[x], b = h2[x];
okk |= ((a + h * pw[len[x]]) % P != (b * pw[d] + h) % P);
}
}
if (okk) return;
for (int i = 0; i < 4 && !okk; i++) {
int v = T[x][i];
if (fl[x][i]) dfs3(v, d + 1, (h * B + i + 1) % P);
}
}
int main() {
pw[0] = 1;
for (int i = 1; i <= 2000000; i++) pw[i] = pw[i - 1] * B % P;
cin >> n; Clear();
for (int i = 1; i <= n; i++) cin >> str[i], Insert(str[i]);
Build();
for (int i = 1; i <= n; i++) {
int p = sz = 0, lst = -1;
for (auto v : str[i]) {
p = T[p][v - 'A']; stk[++sz] = p;
if (ed[p] != inf) lst = ed[p], sz -= ed[p], p = stk[sz];
}
ok[i] = (sz || lst == (int)str[i].size());
}
Clear();
for (int i = 1; i <= n; i++) ok[i] ? Insert(str[i]) : 0;
Build();
for (int i = 1; i <= n; i++) {
if (!ok[i]) continue;
int p = 0, mn = str[i].size();
for (auto v : str[i]) p = T[p][v - 'A'], mn = min(mn, ed[p]);
if (mn != (int)str[i].size()) return cout << "Yes\n", 0;
}
Clear();
for (int i = 1; i <= n; i++) {
if (!ok[i]) continue;
int p = Insert(str[i]); ++c1[p], ++c2[p], len[p] = str[i].size();
for (int j = 0; j < (int)str[i].size(); j++) h1[p] = (h1[p] * B + str[i][j] - 'A' + 1) % P;
h2[p] = h1[p];
}
build();
dfs1(0), dfs2(0);
dfs3(0, 0, 0);
cout << (okk ? "Yes\n" : "No\n");
return 0;
}
极端值原理:判断是否存在,考虑所求中的特殊元素:最小值,最大值,等。

浙公网安备 33010602011771号