[NOI2018] 你的名字
Description
给定一个文本串 \(s\) 和询问次数 \(q\) ,每次给定小文本串 \(t\) 和一个区间 \(l\) 和 \(r\) ,求 \(t\) 有多少子串不是 \(s\) 在区间 \([l,\ r]\) 内的子串。
前 \(68\%\) 的数据区间是 \([1,\ |s|]\) 。
\(|s|,\ |t| \leq 5 \cdot 10 ^ 5,\ \ q\leq 10 ^ 5,\ \ \sum |t| \leq 10 ^ 6\)
Solution
\(68\ {\rm pts}\)
显然先对 \(s\) 建立 \({\rm SAM}\) ,反正询问都是对于整个串而言的。并且还可以对所有 \(t\) 建立 \({\rm SAM}\) ,都是可以接受的。
我们发现假如我们要选择 \(t\) 的所有 \({\rm SAM}\) 建好之后才去计算的话,涉及的步骤有亿点点麻烦(具体搞法可以去俺的博客这里找前半部分)。那我们想想能不能在建立的过程中就记录一些必要的信息呢??
能!
我们想要的仅是在第 \(i\) 个字符加进来后 \(endpos\) 第一个元素是 \(i\) 的那些子串。因为 \(link\) 上面所有子串在之前出现过,也必定在 \(i\) 处出现,所以在更靠前的点上已经能记录贡献了,要刨掉这种。
所以在加完第 \(i\) 个字符之后,紧接着记录父亲的最大子串长度,即 \(len(link(cur))\) 的值为数组 \(ha(i)\) ,表示这个状态前要刨掉的长度。正好此时 \(endpos\) 第一个元素为 \(i\) 的子串必然已全部出现,防止后面有 \(clone\) 状态捣乱。
所以我们只需要正常的向求最大公共子串那样求就行了,求完以此把贡献加上 \(lth - ha(i)\) 。
Code1
Code1
/*
这是 68 pts 的代码
scbamgepe
2
sgepe 1 9
sgepe 1 9
*/
#include
using namespace std;
typedef long long ll;
const int N = 2e6 + 10;
ll ans, res, ha[N];
inline int read() {
char ch = getchar();
int s = 0, w = 1;
while (!isdigit(ch)) {if (ch == '-') w = -1; ch = getchar();}
while (isdigit(ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar();}
return s * w;
}
struct SAM {
int n, cnt, las, len[N], link[N], ch[N][26];
char s[N]; int mx[N];
inline void init() {
cnt = las = 1;
memset(ch[1], 0, sizeof(ch[1]));
}
inline void SAM_stru(int c) {
int cur = ++cnt, p = las;
memset(ch[cur], 0, sizeof(ch[cur]));
las = cur;
len[cur] = len[p] + 1;
while (p && !ch[p][c]) ch[p][c] = cur, p = link[p];
if (!p) {link[cur] = 1; return ;}
int q = ch[p][c];
if (len[p] + 1 == len[q]) {link[cur] = q; return ;}
int clo = ++cnt;
link[clo] = link[q]; len[clo] = len[p] + 1;
link[q] = link[cur] = clo;
memcpy(ch[clo], ch[q], sizeof(ch[clo]));
while (p && ch[p][c] == q) ch[p][c] = clo, p = link[p];
}
} s, t;
inline void mian() {
scanf("%s", t.s); t.n = strlen(t.s); t.init(); ans = res = 0;
int l = read(), r = read();
if (l != 1 || r != s.n) return ;
for (int i = 0; i < t.n; ++i) t.SAM_stru(t.s[i] - 'a'), ha[i] = t.len[t.link[t.las]];
int v = 1, lth = 0;
for (int i = 0, c; i < t.n; ++i) {
c = t.s[i] - 'a';
if (s.ch[v][c]) v = s.ch[v][c], ++lth;
else {
while (v && !s.ch[v][c]) v = s.link[v];
if (v) lth = s.len[v] + 1, v = s.ch[v][c];
else v = 1, lth = 0;
}
ans += max(lth - ha[i], 0ll);
}
for (int i = 1; i <= t.cnt; ++i) {
res += t.len[i] - t.len[t.link[i]];
}
printf("%lld\n", res - ans);
}
int main() {
scanf("%s", s.s); s.n = strlen(s.s); s.init();
for (int i = 0; i < s.n; ++i) s.SAM_stru(s.s[i] - 'a');
int T = read();
while (T--) mian();
return 0;
}
\(100\ {\rm pts}\)
我们发现转到区间上的时候,原本 \({\rm SAM}\) 上的每个状态里的 \(endpos\) 不一定每个都满足;并且可能区间 \({\rm SAM}\) 的真正样子会与全串 \({\rm SAM}\) 大相径庭;还有可能自己没有贡献,但是因为 \(link\) 多了一个很关键的 \(endpos\) 让 \(link\) 会有贡献。
大概是这三个大问题,我们来分别解决。
- \(endpos\) 不能全部匹配
-
实际上只要我们能够快速得到一个状态的 \(endpos\) ,或者说只要能维护单状态在区间内最大的 \(endpos\) 位置就行。
-
直接干算肯定不现实,想办法利用 \({\rm SAM}\) 上 \(link\) 之间的关系,所以可以考虑线段树合并。
-
这样的话每次失配跳 \(link\) 的时候再加一个判断在区间内是否存在一个 \(endpos\) 合法就行。
- 真实样貌大相径庭
- 意思就是说,我们如果直接跳 \(link\) 可能因为形态不一样导致问题。但是似乎这种写法不会有问题,因为每个结点的贡献彼此分离,有贡献就有,没有就没有。
- 自己没有贡献 \(link\) 有
-
大概就是说找到的 \(endpos\) 因为串长太大会被 \(L\) 限制导致干不过 \(ha\) ,从而没有贡献。但是 \(link\) 可能有一个更好的 \(endpos\) 或者说长度减小到有贡献了。所以说我们目前被 \(L\) 限制的话,继续跳 \(link\) 算贡献。
-
时间复杂度均摊是线性,然鹅这个 sb 不会证,所以就咕了。
至此,问题已经全部解决,这道题也就搞定啦!
Code
Code2
/*
scbamgepe
2
sgepe 1 9
sgepe 1 9
*/
#include
using namespace std;
typedef long long ll;
const int N = 2e6 + 10, M = 4e7 + 10;
ll ans, res, ha[N], tr[M];
int rt[N], tot, ls[M], rs[M];
inline int read() {
char ch = getchar();
int s = 0, w = 1;
while (!isdigit(ch)) {if (ch == '-') w = -1; ch = getchar();}
while (isdigit(ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar();}
return s * w;
}
inline void pushup(int now) {tr[now] = max(tr[ls[now]], tr[rs[now]]);}
void modify(int &now, int lt, int rt, int it) {
if (!now) tr[now = ++tot] = -1;
if (lt == rt) {
tr[now] = lt; return ;
}
int mid = (lt + rt) >> 1;
if (it <= mid) modify(ls[now], lt, mid, it);
else modify(rs[now], mid + 1, rt, it);
pushup(now);
}
int query(int now, int lt, int rt, int L, int R) {
if (!now) return -1;
if (L <= lt && rt <= R) return tr[now];
int mid = (lt + rt) >> 1, res = -1, ret = -1;
if (L <= mid) res = query(ls[now], lt, mid, L, R);
if (R > mid) ret = query(rs[now], mid + 1, rt, L, R);
return max(res, ret);
}
int merge(int u, int v, int lt, int rt) {
if (!u) return v;
if (!v) return u;
if (lt == rt) return tr[u] |= tr[v], u;
int mid = (lt + rt) >> 1, w = ++tot;
ls[w] = merge(ls[u], ls[v], lt, mid);
rs[w] = merge(rs[u], rs[v], mid + 1, rt);
tr[w] = max(tr[u], tr[v]);
return w;
}
struct SAM {
int n, cnt, las, len[N], link[N], ch[N][26];
char s[N]; int tong[N], rk[N], mx[N];
inline void init() {
cnt = las = 1;
memset(ch[1], 0, sizeof(ch[1]));
}
inline void SAM_stru(int c) {
int cur = ++cnt, p = las;
memset(ch[cur], 0, sizeof(ch[cur]));
las = cur;
len[cur] = len[p] + 1;
while (p && !ch[p][c]) ch[p][c] = cur, p = link[p];
if (!p) {link[cur] = 1; return ;}
int q = ch[p][c];
if (len[p] + 1 == len[q]) {link[cur] = q; return ;}
int clo = ++cnt;
link[clo] = link[q]; len[clo] = len[p] + 1;
link[q] = link[cur] = clo;
memcpy(ch[clo], ch[q], sizeof(ch[clo]));
while (p && ch[p][c] == q) ch[p][c] = clo, p = link[p];
}
inline void Tong_sort() {
for (int i = 2; i <= cnt; ++i) ++tong[len[i]];
for (int i = 2; i <= cnt; ++i) tong[i] += tong[i - 1];
for (int i = 2; i <= cnt; ++i) rk[tong[len[i]]--] = i;
for (int i = cnt, v, u; i >= 2; --i) {
v = rk[i]; u = link[v];
rt[u] = merge(rt[u], rt[v], 0, n - 1);
}
}
} s, t;
inline void solve(int l, int r) {
int v = 1, lth = 0;
for (int i = 0, c, p, q; i < t.n; ++i) {
c = t.s[i] - 'a';
while (v && (!s.ch[v][c] || (p = query(rt[s.ch[v][c]], 0, s.n - 1, l, r)) == -1))
v = s.link[v], lth = s.len[v];
if (!v) {v = 1, lth = 0; continue;}
++lth; v = s.ch[v][c];
ll mat = min(p - l + 1, lth);
ans += max(mat - ha[i], 0ll);
if (lth > p - l + 1) {
mat = max(mat, ha[i]);
int u = s.link[v], let = s.len[u];
while (u) {
q = query(rt[u], 0, s.n - 1, l, r);
ll mac = min(q - l + 1, let);
ans += max(mac - mat, 0ll);
if (mac <= mat && let <= q - l + 1) break;
mat = max(mac, ha[i]);
u = s.link[u]; let = s.len[u];
}
}
}
for (int i = 1; i <= t.cnt; ++i) {
res += t.len[i] - t.len[t.link[i]];
}
printf("%lld\n", res - ans);
}
inline void mian() {
scanf("%s", t.s); t.n = strlen(t.s); t.init(); ans = res = 0;
int l = read(), r = read();
for (int i = 0; i < t.n; ++i) {
t.SAM_stru(t.s[i] - 'a');
ha[i] = t.len[t.link[t.las]];
}
if (l != 1 || r != s.n) return solve(l - 1, r - 1), void();
int v = 1, lth = 0;
for (int i = 0, c; i < t.n; ++i) {
c = t.s[i] - 'a';
if (s.ch[v][c]) v = s.ch[v][c], ++lth;
else {
while (v && !s.ch[v][c]) v = s.link[v];
if (v) lth = s.len[v] + 1, v = s.ch[v][c];
else v = 1, lth = 0;
}
ans += max(lth - ha[i], 0ll);
}
for (int i = 1; i <= t.cnt; ++i) {
res += t.len[i] - t.len[t.link[i]];
}
printf("%lld\n", res - ans);
}
int main() {
scanf("%s", s.s); s.n = strlen(s.s); s.init();
for (int i = 0; i < s.n; ++i) {
s.SAM_stru(s.s[i] - 'a');
modify(rt[s.las], 0, s.n - 1, i);
}
s.Tong_sort();
int T = read();
while (T--) mian();
return 0;
}