CF316G Good Substrings
直接讲 G3。
这题怎么才 2400 啊 /xia,2400 都不会了 /fn。加训 /fendou。
给出字符串 \(s\),以及 \(n\) 个限制,每个限制形如 \(t_i\texttt{ }l_i\texttt{ }r_i\),一个字符串满足该条限制,当且仅当它在字符串 \(t_i\) 中的出现次数在 \([l_i,r_i]\) 之间。
求 \(s\) 有多少个本质不同的子串满足所有限制。
\(|s|,\max\limits_{i=1}^n |t_i|\le 5\times 10^4\),\(\boldsymbol{n\le 10}\)。
记 \(s[l,r]\) 为字符串 \(s\) 以 \(s_l\) 开头,以 \(s_r\) 结尾的子串,形式化的,\(s[l,r]=\overline{s_l\dots s_r}\)。
看到「本质不同子串」,想到后缀数组。先将所有字符串用奇怪字符拼起来(记大串为 \(S\))做后缀排序求出 \(sa,rk,\text{height}\) 数组并对 \(\text{height}\) 数组维护 ST 表。
对于一个字符串 \(s\),我们知道排名为 \(i\) 的后缀带来的本质不同子串是 \(s[sa_i,\text{height}_i+1]\sim s[sa_i,|s|]\) 这些,然后你会发现这些子串的出现次数随着长度递增不升。
因为若有一个长串出现若干次,我的短串也被这个长串包含,至少出现了这么多次。
考虑二分出最短的满足所有限制上界的字符串长度 \(ans_l\) 以及最长的满足所有限制下界的字符串长度 \(ans_r\),那么这个后缀就可以带来 \(ans_r-ans_l+1\) 个满足所有限制的本质不同子串。
维护一个前缀和数组 \(sum_{j,i}\),表示排名为 \(1\sim i\) 的后缀中,有多少个后缀是 \(t_j\) 带来的。我们又知道对于一个子串,出现它的后缀的排名是一段连续的区间。套路用二分和 ST 表求 \(\text{LCP}\) 得到这个区间 \([L,R]\)。问题变成了判断区间内某个数 \(i\) 出现次数是否(不)超过给定的值。用 \(sum_{i,R}-sum_{i,L-1}\) 表示出其出现次数,由于 \(n\) 很小,枚举判断即可。这样这题基本就做完了。
还有一个地方需要注意,我们要求的是原串 \(\boldsymbol{s}\) 某个后缀带来的本质不同子串个数,但是现在把所有字符接在一起,\(\text{height}\) 数组并是大串 \(S\) 中两个排名相邻的后缀的 \(\text{LCP}\)。所以我们要按排名枚举 \(S\) 的后缀,如果它是 \(s\) 的后缀就统计答案。然后你对 \(S\) 后缀排序,原串 \(\boldsymbol{s}\) 中的所有后缀也是有序的,不然它们在 \(S\) 中也是无序的,相当于你就没排序。 因此直接记录上一个为原串中的某个后缀的排名 \(la\),那么我要的 \(\text{height}\),原串 \(s\) 中排名相邻两个后缀的 \(\text{LCP}\),就是 \(S\) 中 \(S[la,|S|]\) 和当前后缀的 \(\text{LCP}\)。后面接的东西不影响,因为你接了奇怪字符,在那一位一定失配,不会影响 \(\text{LCP}\) 长度。
时间复杂度为 \(\mathcal{O}(|S|\log |s| (n+\log|S|))\),空间复杂度为 \(\mathcal{O}(|S|\log |S|+n|S|)\)。
代码(为啥我写的那么长):
#include <bits/stdc++.h>
#define ll long long
#define P pair
#define fi first
#define se second
using namespace std; const int N = 5e5 + 5; ll ans; string s; int n, id[N], a[N];
int ord = 'z', len, ql[N], qr[N], sum[11][N], hd[N], siz[N], k; bool tag[N];
struct SuffixArray {
int cnt[N], sa[N], rk[N], ht[N], st[20][N], lg[N];
P<P<int, int>, int> p[N], tmp[N];
void init() {
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= len; ++i) ++cnt[a[i]];
for (int i = 1; i <= ord; ++i) cnt[i] += cnt[i - 1];
for (int i = 1; i <= len; ++i) rk[i] = cnt[a[i] - 1] + 1;
}
void sort() {
for (int l = 1, id; l <= len; l <<= 1) {
for (int i = 1; i <= len; ++i)
p[i] = {{rk[i], i + l > len ? 0 : rk[i + l]}, i};
memset(cnt, id = 0, sizeof cnt);
for (int i = 1; i <= len; ++i) ++cnt[p[i].fi.se];
for (int i = 1; i <= len; ++i) cnt[i] += cnt[i - 1];
for (int i = len; i >= 1; --i) tmp[cnt[p[i].fi.se]--] = p[i];
for (int i = 1; i <= len; ++i) p[i] = tmp[i];
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= len; ++i) ++cnt[p[i].fi.fi];
for (int i = 1; i <= len; ++i) cnt[i] += cnt[i - 1];
for (int i = len; i >= 1; --i) tmp[cnt[p[i].fi.fi]--] = p[i];
for (int i = 1; i <= len; ++i) p[i] = tmp[i];
for (int i = 1; i <= len; ++i)
{ if (i == 1 || p[i].fi != p[i - 1].fi) ++id; rk[p[i].se] = id; }
if (id == len) break;
}
for (int i = 1; i <= len; ++i) sa[rk[i]] = i;
}
void height() {
for (int i = 1, k = 0; i <= len; ++i) {
if (rk[i] == 1) { ht[i] = k = 0; continue; }
if (k) --k; int j = sa[rk[i] - 1];
while (i + k <= len && j + k <= len && a[i + k] == a[j + k]) ++k;
ht[i] = k;
}
}
void ST() {
for (int i = 1; i <= len; ++i) st[0][i] = ht[sa[i]], lg[i] = __lg(i);
for (int i = 1; (1 << i) <= len; ++i)
for (int j = 1; j + (1 << i) - 1 <= len; ++j)
st[i][j] = min(st[i - 1][j], st[i - 1][j + (1 << (i - 1))]);
}
int LCP(int l, int r) {
if (l == r) return len - sa[l] + 1; ++l;
int k = lg[r - l + 1]; return min(st[k][l], st[k][r - (1 << k) + 1]);
}
} SA;
bool check1(int id, int x) {
int l = 1, r = id, ansl, ansr;
while (l <= r) {
int mid = (l + r) >> 1;
if (SA.LCP(mid, id) >= x) ansl = mid, r = mid - 1; else l = mid + 1;
}
l = id, r = len;
while (l <= r) {
int mid = (l + r) >> 1;
if (SA.LCP(id, mid) >= x) ansr = mid, l = mid + 1; else r = mid - 1;
}
for (int i = 1; i <= n; ++i)
if (sum[i][ansr] - sum[i][ansl - 1] > qr[i]) return 0;
return 1;
}
bool check2(int id, int x) {
int l = 1, r = id, ansl, ansr;
while (l <= r) {
int mid = (l + r) >> 1;
if (SA.LCP(mid, id) >= x) ansl = mid, r = mid - 1; else l = mid + 1;
}
l = id, r = len;
while (l <= r) {
int mid = (l + r) >> 1;
if (SA.LCP(id, mid) >= x) ansr = mid, l = mid + 1; else r = mid - 1;
}
for (int i = 1; i <= n; ++i)
if (sum[i][ansr] - sum[i][ansl - 1] < ql[i]) return 0;
return 1;
}
signed main() {
cin.tie(0), cout.tie(0), ios::sync_with_stdio(0);
cin >> s >> n; len = k = s.size();
for (int i = 1; i <= len; ++i) a[i] = s[i - 1]; a[++len] = ++ord;
for (int i = 1; i <= n; ++i) {
cin >> s >> ql[i] >> qr[i]; siz[i] = s.size(); hd[i] = len + 1;
for (int j = 0; j < siz[i]; ++j) a[++len] = s[j];
for (int j = len; j > len - siz[i]; --j) a[++len] = ++ord;
}
SA.init(); SA.sort(); SA.height(); SA.ST();
for (int i = 1; i <= len; ++i) if (SA.sa[i] <= k) tag[i] = 1;
for (int i = 1; i <= n; ++i)
for (int j = hd[i]; j < hd[i] + siz[i]; ++j) sum[i][SA.rk[j]] = 1;
for (int i = 1; i <= len; ++i)
for (int j = 1; j <= n; ++j) sum[j][i] += sum[j][i - 1];
for (int i = 1, la = 0; i <= len; ++i) {
if (!tag[i]) continue;
int l = (la ? SA.LCP(la, i) : 0) + 1, r = k - SA.sa[i] + 1;
int ansl = -1, ansr = -1;
while (l <= r) {
int mid = (l + r) >> 1;
if (check1(i, mid)) ansl = mid, r = mid - 1; else l = mid + 1;
}
l = (la ? SA.LCP(la, i) : 0) + 1, r = k - SA.sa[i] + 1; la = i;
while (l <= r) {
int mid = (l + r) >> 1;
if (check2(i, mid)) ansr = mid, l = mid + 1; else r = mid - 1;
}
if (ansl == -1 || ansr == -1) continue;
if (ansl > ansr) continue; ans += ansr - ansl + 1;
}
cout << ans; return 0;
}

浙公网安备 33010602011771号