标签:oid 情况 子集 使用 long 过程 emc bool 方式
给定一个一个文本串 \(A(|A| \le 500)\),以及一个大小为 \(m\) 的模式串集 \(B = \{B_1, B_2, \cdots, B_m\}(m\le 10^5, |B_i| \le |A|, \sum|B_i| \le 10^6)\)。试选出一个 \(B\) 的子集 \(S\),满足 \(S\) 中的字符串存在一种排列方式(可以任意排列),使排列后的新字符串 \(C\) 无限向两端复制的结果等价于 \(A\) 向两端无限复制的结果。求 \(\min |S|\)。
非常显然,若 \(S\) 可以匹配到 \(A\) 的一个循环节,那么整个都可以匹配。而 \(A\) 的一个循环节无限复制的结果和 \(A\) 本身复制的结果是相同的。于是我们之间匹配 \(A\) 的 最小循环节 即可。记这个最小循环节长度为 \(L\)。
我们处理出 \(B\) 中的模式串在 \(A\) 中的出现情况。对于 \(A\) 的某一个位置 \(i\),若 \(A[i, j]\) 是 \(B\) 中的一个串,那么我们称 第 \(i\) 个位置可以转移到第 \(j+ 1\) 个位置。但可能会超出最小循环节,于是令 \(j\) 对 \(L\) 取模。
考虑如何对于所有的 \(i \in [1, L]\),处理出所有从 \(i\) 开始的转移。由于转移中的 \(B\) 一定是 \(A[i, |A|]\) 的 一个前缀,当一个位置断掉了,之后一定不会匹配。可以用 Trie 树辅助实现这一过程。
现在我们已经得到了所有转移,那么我们不妨 将位置视为点,将转移视为边,构成了一个有向图。 其中每使用一次转移,就相当于使用 \(B\) 中 的一个元素。这个元素并不会重用,因为处理出了 \(A\) 的最小循环节。
那么我们要求的答案就是 最小环的大小。这个直接 Floyd 可以过,复杂度 \(O(n^3)\)。
思路主要来自 kqh (orz)
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Article : ZJU Summer Training 2020
*/
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 1005;
const int L = 1e6 + 5;
const int M = 1e5 + 5;
const int S = 26;
int n, m;
char s[N];
char t[N];
int g[N][N];
int f[N][N];
struct trie {
int ch[L][S];
int total;
bool end[L];
trie() {
total = 1;
}
void insert(char* s) {
int x = 1;
for (int i = 0; s[i]; i++) {
int c = s[i] - ‘a‘;
if (!ch[x][c]) ch[x][c] = ++total;
x = ch[x][c];
}
end[x] = 1;
}
inline int* operator [] (int p) {
return ch[p];
}
} tr;
bool same(char* a, char* b, int l){
for (; l; --l, ++a, ++b) if (*a != *b) return false;
return true;
}
signed main() {
scanf("%s", s + 1);
n = strlen(s + 1);
int cl = n;
for (int l = n; l; --l) {
if (n % l != 0) continue;
bool f = 1;
for (register int start = l + 1; start <= n && f; start += l)
if (!same(s + 1, s + start, l)) f = false;
if (f) cl = l;
}
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
scanf("%s", t);
tr.insert(t);
}
copy(s + 1, s + 1 + n, s + 1 + n);
memset(g, 0x3f, sizeof g);
for (int i = 1; i <= cl; i++) {
int x = 1;
for (int j = i; j <= n + i; j++) {
x = tr[x][s[j] - ‘a‘];
if (!x) break;
if (tr.end[x]) {
int k = j % cl + 1;
if (k == i) { puts("1"); return 0;}
g[i][k] = 1;
}
}
}
for (register int i = 1; i <= cl; i++)
g[i][i] = 0;
memcpy(f, g, sizeof g);
long long ans = 0x3f3f3f3f;
for(int k = 1; k <= cl; k++)
for(int i = 1; i <= cl; i++)
for(int j = 1; j <= cl; j++)
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
for(int i = 1; i <= cl; i++)
for(int j = 1; j <= cl; j++)
if (i != j)
ans = min(ans, 1ll * f[i][j] + f[j][i]);
printf("%lld\n", ans > n * 2 ? -1 : ans);
return 0;
}
「ZJU Summer Training 2020 - Round 2/3」部分补题记录
标签:oid 情况 子集 使用 long 过程 emc bool 方式
原文地址:https://www.cnblogs.com/-Wallace-/p/13329319.html