[NOI2018]你的名字
题目链接
先考虑68分的做法:
求在A串中出现,且在B串中没出现的串的数量。
使用容斥,用A的不同子串数减去A,B的不同公共子串数。
先用双指针,求出A的每个位置开始,在B中最多能向后匹配多远。
然后,问题变为,给你一些区间,问它们的子区间中有多少不同的串。
因为每个串,都是原区间\([l,r]\)中\([l,i]\)的后缀。\((l<=i<=r)\)
而后缀就是在前面去掉一些字符,就是不断走fa。
所以可以建出fa边的树,然后定位出这些位置,求这些点到根的路径的并。
使用树链合并即可,要注意压缩的问题。
100分做法:与68基本相同,就是判断子串\([a,b]\)是否出现多了一个限制(\(l<=a,b<=r\))。
而一个串匹配的位置,就是这个点子树中红点的集合。
红点对应前缀,所以这个限制可以转化为前缀位置的限制。
需要判断一个点的子树中是否有x~y的数。使用DFS序+主席树即可。
代码:
#include <stdio.h>
#include <stdlib.h>
#define ll long long
int fr[2000010],ne[2000010];
int v[2000010],bs = 0;
int wl[2000010],wr[2000010],tm = 1;
int xl[2000010];
struct SAM {
int trs[2000010][26],fa[2000010];
int len[2000010],np,sl;
int red[2000010];
SAM() {
np = sl = 1;
}
void clean() {
for (int i = 1; i <= sl; i++) {
fa[i] = len[i] = 0;
red[i] = false;
for (int j = 0; j < 26; j++) trs[i][j] = 0;
}
np = sl = 1;
}
void insert(char c, int wz) {
c -= 'a';
int p = np;
np = ++sl;
len[np] = len[p] + 1;
while (p != 0 && trs[p][c] == 0) {
trs[p][c] = np;
p = fa[p];
}
if (p == 0) fa[np] = 1;
else {
int q = trs[p][c];
if (len[q] == len[p] + 1) fa[np] = q;
else {
int nq = ++sl;
fa[nq] = fa[q];
fa[q] = fa[np] = nq;
len[nq] = len[p] + 1;
for (int j = 0; j < 26; j++) trs[nq][j] = trs[q][j];
while (p != 0 && trs[p][c] == q) {
trs[p][c] = nq;
p = fa[p];
}
}
}
red[np] = wz;
}
};
void addb(int a, int b) {
v[bs] = b;
ne[bs] = fr[a];
fr[a] = bs;
bs += 1;
}
SAM S,T;
void dfs1(int u) {
xl[tm] = u;
wl[u] = tm++;
for (int i = fr[u]; i != -1; i = ne[i]) dfs1(v[i]);
wr[u] = tm;
}
int up[1000010];
char zf[1000010];
struct SPx {
int u,cd;
SPx() {}
SPx(int U, int Cd) {
u = U;
cd = Cd;
}
};
SPx px[1000010];
ll baoli(int s) {
for (int i = 1; i <= T.sl; i++) up[i] = 0;
for (int i = 0; i < s; i++) {
int cd = px[i].cd,
u = px[i].u;
while (u != 1) {
if (up[u] == T.len[u] - T.len[T.fa[u]]) break;
if (cd - T.len[T.fa[u]] > up[u]) up[u] = cd - T.len[T.fa[u]];
u = T.fa[u];
cd = T.len[u];
}
}
ll rtn = 0;
for (int i = 2; i <= T.sl; i++) rtn += up[i];
return rtn;
}
int he[24000010],cl[24000010],cr[24000010],sl = 0;
int jianshu(int l, int r) {
int rt = sl++;
he[rt] = 0;
if (l + 1 == r) return rt;
int m = (l + r) >> 1;
cl[rt] = jianshu(l, m);
cr[rt] = jianshu(m, r);
return rt;
}
int xiugai(int i, int l, int r, int j, int x) {
int rt = sl++;
if (l + 1 == r) {
he[rt] = he[i] + x;
return rt;
}
cl[rt] = cl[i];
cr[rt] = cr[i];
int m = (l + r) >> 1;
if (j < m) cl[rt] = xiugai(cl[rt], l, m, j, x);
else cr[rt] = xiugai(cr[rt], m, r, j, x);
he[rt] = he[cl[rt]] + he[cr[rt]];
return rt;
}
int chaxun(int i, int l, int r, int L, int R) {
if (R <= l || r <= L) return 0;
if (L <= l && r <= R) return he[i];
int m = (l + r) >> 1;
return chaxun(cl[i], l, m, L, R) + chaxun(cr[i], m, r, L, R);
}
int gen[1000010],n;
bool zichuan(int i, int l, int r, int cd) {
l = l + cd - 1;
if (l > r) return false;
int z = chaxun(gen[wr[i] - 1], 1, n + 1, l, r + 1) - chaxun(gen[wl[i] - 1], 1, n + 1, l, r + 1);
return z > 0;
}
int main() {
scanf("%s", zf);
for (n = 0; zf[n] != 0; n++) S.insert(zf[n], n + 1);
for (int i = 1; i <= S.sl; i++) fr[i] = -1;
for (int i = 1; i <= S.sl; i++) addb(S.fa[i], i);
dfs1(1);
gen[0] = jianshu(1, n + 1);
for (int i = 1; i < tm; i++) {
if (S.red[xl[i]] != 0) gen[i] = xiugai(gen[i - 1], 1, n + 1, S.red[xl[i]], 1);
else gen[i] = gen[i - 1];
}
int m;
scanf("%d", &m);
for (int i = 0; i < m; i++) {
int l,r,x = 1,y = 1,s = 0;
scanf("%s%d%d", zf, &l, &r);
T.clean();
for (int j = 0; zf[j] != 0; j++) T.insert(zf[j], 0);
for (int j = 0, k = -1, c = 0; zf[j] != 0; j++) {
c -= 1;
if (x != 1 && k - j == S.len[S.fa[x]]) x = S.fa[x];
if (y != 1 && k - j == T.len[T.fa[y]]) y = T.fa[y];
if (k < j) k = j,
c = 0;
while (zf[k] != 0 && S.trs[x][zf[k] - 'a'] != 0 && zichuan(S.trs[x][zf[k] - 'a'], l, r, c + 1)) {
c += 1;
x = S.trs[x][zf[k] - 'a'];
y = T.trs[y][zf[k] - 'a'];
px[s++] = SPx(y, c);
k += 1;
}
}
ll zo = 0;
for (int j = 1; j <= T.sl; j++) zo = zo + T.len[j] - T.len[T.fa[j]];
printf("%lld\n", zo - baoli(s));
}
return 0;
}