字符串
Description
令
s
与
w[l,r]
表示字符串
w
在区间
w
在
f(s,w,l,r)
表示
w[l,r]
在
s
中出现的频率。
比如
现在给定串
s
,
∑i=abf(s,w,li,ri)
Input
第一行四个整数
n,m,q,k
,
n
表示
接下来一行一个长为
s
的字符串
接下来
m
行,每行两个整数表示
接下来
q
行,每行一个字符串
Output
对于每个询问一行,输出答案。
Sample Input
8 5 3 3
abacdaba
0 2
1 2
0 0
2 2
1 2
dab 1 4
bac 2 3
eeb 1 3
Sample Output
7
3
2
Hint
对于
10%
的数据,
n,m,k,q≤10
;
对于
30%
的数据,满足
n,m,k,q≤102
对于
50%
的数据,满足
n,m,k,q≤104
;
对于
100%
的数据,满足
n,m,k,q≤105,∑w≤105
,字符串由小写英文字母构成。
神题不解释……
蒟蒻只能想到前一半莫队…..
然而当时考的时候连后缀自动机都不会
另外咱很久没打后缀自动机以及莫队了,今天两个方法按定义脑补居然都一遍过了……真不可思议……
思路:
咱要是说乍一看毫无思路呢……
好吧,事实上,看题目要求出现次数就可以看出来需要后缀自动机了。
然后显然咱需要right集合的大小。如果不知道right集合是什么那咱也没办法。
然后呢?
难以想到对所有数据通用的算法,考虑分部分针对数据设计算法。
观察到
∑w≤105
,考虑据此设计针对性算法。
先考虑
k
较小的情况。
特点是串的数量很多但长度不大,需采用同时回答大量询问,但允许
考虑一个奇妙的方法:莫队。
可以发现,咱可以采用
O(k2)
的时间计算所有区间的贡献。
如果此时咱用莫队离线维护最后的那些询问,每次记一个
cnt[i][j]
表示之前的询问中有多少管辖区间为
[i,j]
。
那么每次
O(k2)
枚举每个区间,每个区间得到的答案乘上有几个询问覆盖了此区间就是这个区间的贡献~
复杂度
O(n1.5k2)
。
发现小数据快如闪电……
然而k一大就尴尬了。
考虑这样的数据的特征:串的数量少。
那么可以考虑使用一个复杂度与
k
关系较小的算法,要求快速处理单次询问,与长度关系较小。
这里使用了倍增。
可以发现匹配原串的某个前缀的某个后缀(也就是某个子串),只需要从这个前缀当前所在端点开始,不断沿fail指针跳,直到调到的节点长度恰好等于这个后缀的长度,此时所在节点的right的大小即为答案。
那么考虑到fail指针连成了一棵叫做fail树的东西,用倍增维护这棵树进行查询即可~
具体做法是,对于每个后面的询问,在后缀自动机上跑一边原串,每走到一个字符便使用倍增查询所有右端点为这个字符的之前的询问即可~
复杂度
以
sqrt(n)
附近的某个值作为两种算法的使用分界线,小于用莫队,大于用倍增即可~
看着是不是一股不能过的气息……然而跑得飞快~
下面那个mo是莫队,但倍增咱不知道叫什么于是随手故意取了一个~
#include<iostream>
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
inline int read()
{
int x=0;char ch=getchar();
while(ch<'0' || '9'<ch)ch=getchar();
while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
return x;
}
typedef long long ll;
const int N=1e5+9;
const int K=23;
const int M=26;
const int border=519;
int n,m,q,k;
char s[N];
namespace sam
{
int ch[N<<1][M],fa[N<<1][K],tot,u;
int len[N<<1],siz[N<<1],id[N<<1];
inline void init()
{
u=tot=1;
}
inline void add(int v)
{
int now=++tot;
len[now]=len[u]+1;
siz[now]=1;
while(u && !ch[u][v])
ch[u][v]=now,u=fa[u][0];
if(!u)
fa[now][0]=1;
else
{
int q=ch[u][v];
if(len[q]==len[u]+1)
fa[now][0]=q;
else
{
int newq=++tot;
memcpy(ch[newq],ch[q],sizeof(ch[q]));
len[newq]=len[u]+1;
fa[newq][0]=fa[q][0];
fa[q][0]=fa[now][0]=newq;
while(u && ch[u][v]==q)
ch[u][v]=newq,u=fa[u][0];
}
}
u=now;
}
inline void work()
{
static int cnt[N];
for(int i=1;i<=tot;i++)
cnt[len[i]]++;
for(int i=1;i<=n;i++)
cnt[i]+=cnt[i-1];
for(int i=1;i<=tot;i++)
id[cnt[len[i]]--]=i;
for(int i=tot;i>=1;i--)
siz[fa[id[i]][0]]+=siz[id[i]];
}
inline void build()
{
fa[1][0]=0;
for(int i=1;i<K;i++)
for(int j=1;j<=tot;j++)
fa[j][i]=fa[fa[j][i-1]][i-1];
}
inline int nxt(int &now,int v,int dir)
{
if(ch[now][dir])
{
now=ch[now][dir];
return v+1;
}
while(now && !ch[now][dir])
now=fa[now][0];
v=len[now];
if(ch[now][dir])
v++;
now=ch[now][dir];
if(now==0)now=1;
return v;
}
inline ll query(int now,int val)
{
if(len[now]<val)return 0;
for(int i=K-1;i>=0;i--)
if(len[fa[now][i]]>=val)
now=fa[now][i];
return siz[now];
}
}
struct query
{
int l,r;
}qu[N];
namespace mo
{
using namespace sam;
struct querys
{
int l,r,id;
}que[N];
ll ans[N];
int cnt[border+1][border+1];
char w[N][border+1];
bool cmp(const querys &a,const querys &b)
{
if(a.l/border==b.l/border)
return a.r<b.r;
return a.l/border<b.l/border;
}
int main()
{
for(int i=1;i<=q;i++)
{
scanf("%s",w[i]+1);
que[i].l=read()+1;
que[i].r=read()+1;
que[i].id=i;
}
sort(que+1,que+q+1,cmp);
int l=0,r=0;
for(int i=1;i<=q;i++)
{
while(l<que[i].l)
cnt[qu[l].l][qu[l].r]--,l++;
while(que[i].l<l)
cnt[qu[l-1].l][qu[l-1].r]++,l--;
while(r<que[i].r)
cnt[qu[r+1].l][qu[r+1].r]++,r++;
while(que[i].r<r)
cnt[qu[r].l][qu[r].r]--,r--;
for(int s=1;s<=k;s++)
{
int now=1;
for(int t=s;t<=k;t++)
{
now=ch[now][w[que[i].id][t]-'a'];
if(!now)continue;
ans[que[i].id]+=(ll)cnt[s][t]*siz[now];
}
}
}
for(int i=1;i<=q;i++)
printf("%lld\n",ans[i]);
return 0;
}
}
namespace ha
{
char w[N];
vector<int> wea[N];
int main()
{
sam::build();
for(int cases=1,l,r;cases<=q;cases++)
{
scanf("%s",w+1);
l=read()+1;r=read()+1;
for(int i=l;i<=r;i++)
wea[qu[i].r].push_back(qu[i].r-qu[i].l+1);
ll ans=0;
int now=1,val=0;
for(int i=1;i<=k;i++)
{
val=sam::nxt(now,val,w[i]-'a');
for(int j=0;j<wea[i].size();j++)
{
if(val<wea[i][j])continue;
ans+=sam::query(now,wea[i][j]);
}
wea[i].clear();
}
printf("%lld\n",ans);
}
return 0;
}
}
int main()
{
n=read();m=read();
q=read();k=read();
scanf("%s",s+1);
sam::init();
for(int i=1;i<=n;i++)
sam::add(s[i]-'a');
sam::work();
for(int i=1;i<=m;i++)
qu[i].l=read()+1,qu[i].r=read()+1;
if(k<border)
return mo::main();
else
return ha::main();
return 0;
}