【NOI2018】君の名は(后缀自动机,线段树)

题面——[NOI2018] 你的名字

题目背景

实力强大的小 A 被选为了 ION2018 的出题人,现在他需要解决题目的命名问题。

题目描述

小 A 被选为了 ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了。

由于 ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手册规定:每年由命题委员会规定一个小写字母字符串,我们称之为那一年的命名串,要求每道题的名字必须是那一年的命名串的一个非空连续子串,且不能和前一年的任何一道题目的名字相同。

由于一些特殊的原因,小 A 不知道 ION2017 每道题的名字,但是他通过一些特殊手段得到了 ION2017 的命名串,现在小 A 有 Q Q Q 次询问:每次给定 ION2017 的命名串和 ION2018 的命名串,求有几种题目的命名,使得这个名字一定满足命题委员会的规定,即是 ION2018 的命名串的一个非空连续子串且一定不会和 ION2017 的任何一道题目的名字相同。

由于一些特殊原因,所有询问给出的 ION2017 的命名串都是某个串的连续子串,详细可见输入格式。

输入格式

第一行一个字符串 S S S ,之后询问给出的 ION2017 的命名串都是 S S S 的连续子串。
第二行一个正整数 Q Q Q,表示询问次数。
接下来 Q Q Q 行,每行有一个字符串 T T T 和两个正整数 l , r l,r l,r,表示询问如果 ION2017 的命名串是 S l … r S_{l\ldots r} Slr,ION2018 的命名串是 T T T 的话,有几种命名方式一定满足规定。

输出格式

输出 Q Q Q 行,第 i i i 行一个非负整数表示第 i i i 个询问的答案。

样例 #1

样例输入 #1

scbamgepe
3
smape 2 7
sbape 3 8
sgepe 1 9

样例输出 #1

12
10
4

提示

数据范围

测试点 ∣ S ∣ ≤ | S| \leq S Q ≤ Q\leq Q ∑ ∣ T ∣ ≤ \sum | T| \leq T其他限制
1 1 1 200 200 200 200 200 200 40000 40000 40000 T ≤ 200 T\leq 200 T200
2 2 2 1000 1000 1000 200 200 200 40000 40000 40000 T ≤ 200 T\leq 200 T200
3 3 3 1000 1000 1000 200 200 200 40000 40000 40000 T ≤ 200 T\leq 200 T200
4 4 4 1000 1000 1000 200 200 200 5 × 1 0 5 5 \times 10^5 5×105
5 5 5 1000 1000 1000 200 200 200 5 × 1 0 5 5 \times 10^5 5×105
6 6 6 5 × 1 0 5 5 \times 10^5 5×105 1 1 1 5 × 1 0 5 5 \times 10^5 5×105
7 7 7 5 × 1 0 5 5 \times 10^5 5×105 1 1 1 5 × 1 0 5 5 \times 10^5 5×105
8 8 8 1 0 5 10^5 105 1 0 5 10^5 105 2 × 1 0 5 2 \times 10^5 2×105
9 9 9 1 0 5 10^5 105 1 0 5 10^5 105 2 × 1 0 5 2 \times 10^5 2×105字符串随机
10 10 10 2 × 1 0 5 2 \times 10^5 2×105 1 0 5 10^5 105 4 × 1 0 5 4 \times 10^5 4×105
11 11 11 2 × 1 0 5 2 \times 10^5 2×105 1 0 5 10^5 105 4 × 1 0 5 4 \times 10^5 4×105字符串随机
12 12 12 3 × 1 0 5 3 \times 10^5 3×105 1 0 5 10^5 105 6 × 1 0 5 6 \times 10^5 6×105
13 13 13 3 × 1 0 5 3 \times 10^5 3×105 1 0 5 10^5 105 6 × 1 0 5 6 \times 10^5 6×105字符串随机
14 14 14 4 × 1 0 5 4 \times 10^5 4×105 1 0 5 10^5 105 8 × 1 0 5 8 \times 10^5 8×105
15 15 15 4 × 1 0 5 4 \times 10^5 4×105 1 0 5 10^5 105 8 × 1 0 5 8 \times 10^5 8×105字符串随机
16 16 16 5 × 1 0 5 5 \times 10^5 5×105 1 0 5 10^5 105 1 0 6 10^6 106
17 17 17 5 × 1 0 5 5 \times 10^5 5×105 1 0 5 10^5 105 1 0 6 10^6 106字符串随机
18 18 18 2 × 1 0 5 2 \times 10^5 2×105 1 0 5 10^5 105 1 0 6 10^6 106
19 19 19 3 × 1 0 5 3 \times 10^5 3×105 1 0 5 10^5 105 1 0 6 10^6 106
20 20 20 4 × 1 0 5 4 \times 10^5 4×105 1 0 5 10^5 105 1 0 6 10^6 106
21 21 21 5 × 1 0 5 5 \times 10^5 5×105 1 0 5 10^5 105 1 0 6 10^6 106
22 22 22 5 × 1 0 5 5 \times 10^5 5×105 1 0 5 10^5 105 1 0 6 10^6 106
23 23 23 5 × 1 0 5 5 \times 10^5 5×105 1 0 5 10^5 105 1 0 6 10^6 106
24 24 24 5 × 1 0 5 5 \times 10^5 5×105 1 0 5 10^5 105 1 0 6 10^6 106
25 25 25 5 × 1 0 5 5 \times 10^5 5×105 1 0 5 10^5 105 1 0 6 10^6 106

对于前 17 17 17 个测试点的所有询问有 l = 1 , r = ∣ S ∣ l=1,r=|S| l=1,r=S

对于所有数据,保证 1 ≤ l ≤ r ≤ ∣ S ∣ 1\leq l \leq r \leq |S| 1lrS, 1 ≤ ∣ T ∣ ≤ 5 × 1 0 5 1\leq |T|\leq 5 \times 10^5 1T5×105

题解

简述题意:给定一个模板串 S S S,多组询问,每次给出一个询问字符串 T T T 和一个区间 [ l , r ] [l,r] [l,r],要求你输出 T T T 有多少个本质不同的子串,满足这个子串没有在 S S S [ l , r ] [l,r] [l,r] 这段区间当中出现。

我们可以把每一个串的贡献放到右端点处统计。容易发现,对于 T T T 的每个前缀,与之前的前缀以及 S S S重复的部分恰好是长度不超过某个值的所有后缀。所以我们可以对于 T T T 的每个前缀求出这个长度值。该长度值等于 max ⁡ ( 与 T 前 面 重 复 的 长 度 , 与 S 重 复 的 长 度 ) \max(与 T 前面重复的长度,与 S 重复的长度) max(T,S) 。我们先对于每个 T T T 用后缀自动机求出前一个值,再考虑统一求出后一个值。

对于全串的想法肯定是经典的在后缀自动机上跑,但是如果是子串 [ l , r ] [l,r] [l,r] 的话,我们得模拟出这个子串的 SAM 。通过维护 e n d p o s endpos endpos 集合可以判断 S S S 串的 SAM 每个节点在 [ l , r ] [l,r] [l,r] 中的 l e n g t h length length 元素。

维护 e n d p o s endpos endpos 集合其实只需要维护其中的最大值,所以维护时 [ l , r ] [l,r] [l,r] 的区间限制可以等价于 [ 1 , r ] [1,r] [1,r],我们把询问离线放在右端点上,用扫描线扫 S S S 。建出后缀树,根据 dfs 序建出线段树维护 e n d p o s endpos endpos 的最大值。

原本经典的匹配方法,只需要判断失配,然后跳 f a i l fail fail ,但是由于 l e n g t h length length 实际上会根据 e n d p o s endpos endpos 变化,所以不能直接跳,可能会跳过。我们失配时只需要将当前匹配长度 m l ml ml 减一,直到与父亲的 l e n g t h length length 相等的时候再跳。匹配的条件是存在对应出边 → y \rightarrow y y ,并且 ( max ⁡ e n d p o s y ) − m l ≥ l (\max endpos_y)-ml\geq l (maxendposy)mll ,即 l m lm lm 对于出边的 y y y 是在区间 [ l , r ] [l,r] [l,r] 内合理的。

时间复杂度 O ( ∑ ∣ T ∣ log ⁡ ∣ S ∣ ) O(\sum|T|\log|S|) O(TlogS)

CODE

原本以为为了防止每次从 n n n 退 1 1 1 ,需要在后缀树上进行倍增处理。结果不需要😅

#include<map>
#include<set>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<random>
#include<bitset>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<unordered_map>
// #pragma GCC optimize(2)
// #pragma GCC optimize("Ofast")
using namespace std;
#define MAXN 1000005
#define LL long long
#define ULL unsigned long long
#define ENDL putchar('\n')
#define DB double
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
int xchar() {
	static const int maxn = 1000000;
	static char b[maxn];
	static int pos = 0,len = 0;
	if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
	if(pos == len) return -1;
	return b[pos ++];
}
// #define getchar() xchar()
LL read() {
	LL f = 1,x = 0;int s = getchar();
	while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
	while(s >= '0' && s <= '9') {x = (x<<1) + (x<<3) + (s^48);s = getchar();}
	return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar((x%10)^48);}
void putnum(LL x) {
	if(!x) {putchar('0');return ;}
	if(x<0) putchar('-'),x = -x;
	return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);}

int n,m,s,o,k;
char ss[MAXN],T[MAXN];
int le[MAXN];
vector<char> tt[MAXN];
vector<int> pr[MAXN];
struct SAM{
    int s[26],len,fa;
    SAM(){memset(s,0,sizeof(s));len=fa=0;}
}sam[MAXN];
int las = 1,cnt = 1;
int addsam(int c) {
    int p = las,np = (las = ++ cnt);
    sam[np] = SAM(); sam[np].len = sam[p].len + 1;
    for(;p&&!sam[p].s[c];p=sam[p].fa) sam[p].s[c]=np;
    if(!p) sam[np].fa = 1;
    else {
        int q = sam[p].s[c];
        if(sam[q].len == sam[p].len + 1) sam[np].fa = q;
        else {
            int nq = ++ cnt; sam[nq] = sam[q];
            sam[nq].len = sam[p].len + 1;
            sam[np].fa = sam[q].fa = nq;
            for(;p&&sam[p].s[c]==q;p=sam[p].fa) sam[p].s[c]=nq;
        }
    }return np;
}
vector<int> g[MAXN];
int id[MAXN],l[MAXN],r[MAXN];
int tim;
int dfn[MAXN],rr[MAXN],f[MAXN][20];//19
void dfs(int x,int ff) {
    f[x][0] = ff; dfn[x] = ++ tim;
    for(int i = 1;i <= 19;i ++) f[x][i] = f[f[x][i-1]][i-1];
    for(int &y:g[x]) dfs(y,x);
    rr[x] = tim; return ;
}
int tre[MAXN<<2],M;
void maketree(int n) {M=1;while(M<n+2)M<<=1;}
void addtree(int x,int y) {for(int s=M+x;s;s>>=1)tre[s] = max(tre[s],y);}
int findtree(int l,int r) {
    int as = 0; if(l > r) return as;
    for(int s=M+l-1,t=M+r+1;(s>>1)^(t>>1);s>>=1,t>>=1) {
        if(!(s&1)) as = max(as,tre[s^1]);
        if(t & 1) as = max(as,tre[t^1]);
    } return as;
}
int RR(int x) {return findtree(dfn[x],rr[x]);}
vector<int> bu[MAXN];
LL as[MAXN];
int main() {
    scanf("%s",ss + 1);
    n = strlen(ss + 1);
    m = read();
    for(int i = 1;i <= m;i ++) {
        scanf("%s",T + 1);
        k = strlen(T + 1);
        l[i] = read(); r[i] = read(); bu[r[i]].push_back(i);
        le[i] = k; sam[las=cnt=1] = SAM();
        for(int j = 1;j <= k;j ++) {
            tt[i].push_back(T[j]);
            int p = addsam(T[j]-'a');
            pr[i].push_back(sam[sam[p].fa].len);
        }
    }
    sam[las=cnt=1] = SAM();
    for(int i = 1;i <= n;i ++) {
        id[i] = addsam(ss[i]-'a');
    }
    for(int i = 2;i <= cnt;i ++) g[sam[i].fa].push_back(i);
    dfs(1,0); maketree(tim);
    for(int ii = 1;ii <= n;ii ++) {
        addtree(dfn[id[ii]],ii);
        for(int &i:bu[ii]) {
            // printf("%d:\n",i);
            int p = 1,ll = 0; s = l[i]; o = r[i];
            for(int j = 0;j < le[i];j ++) {
                int c = tt[i][j]-'a';
                // if(p > 1 && sam[sam[p].fa].len >= ll) {
                //     for(int k = 19;k >= 0;k --) {
                //         if(f[p][k] && sam[f[p][k]].len >= ll) p = f[p][k];
                //     }
                // }
                while(p > 1 && (!sam[p].s[c] || RR(sam[p].s[c])-ll < s)) {
                    if(ll <= sam[sam[p].fa].len) p = sam[p].fa;
                    else ll = max(0,ll-1);
                    if(ll <= sam[sam[p].fa].len) p = sam[p].fa;
                }
                if(sam[p].s[c] && RR(sam[p].s[c])-ll >= s) p = sam[p].s[c],ll ++;
                ll = min(ll,min(sam[p].len,RR(p)-s+1));
                as[i] += (j+1) - max(ll,pr[i][j]);
                // printf("    %d: %d(%d)\n",j+1,max(ll,pr[i][j]),p);
            }
        }
    }
    for(int i = 1;i <= m;i ++) {
        AIput(as[i],'\n');
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值