CF666E-Forensic Examination
题意
给出一个主串\(s\),长度为\(n\),再给出\(m\)个字符串\(t_i\),\(q\)次询问,每次询问形如\((a,b,c,d)\),问\(t_a\cdots t_b\)中\(s[c:d]\)在哪个字符串中出现次数最多,出现了多少次(一样次数取编号小的)。
\(n\le 5\times 10^5,m,\sum t_i \le 5\times 10^4, q\le 5\times 10^5\)。
分析
区间询问诶!!
看起来就是一个区间出现次数众数问题,是否可以用线段树解决呢?
有两种思路。第一种,对所有\(t\)串建线段树,线段树上每个节点建一个广义后缀自动机,在里面建出后缀树线段树合并得到每个点的最多出现次数是区间中的哪个,每次查询把问题分成\(\log n\)个区间,在上面查询对应点。这个思路有一个问题,如何多次提取\(s\)的区间。如果我们只做一次的话是可以用倍增解决的,但是如果我们在线段树的每个节点的那个区间中都插入一个\(s\)串显然是空间时间不可行的。于是官方题解(它的代码到现在还没看懂)的做法是只在线段树根节点插入\(s\),在线段树的每个节点的自动机的每个节点上记一个到左右儿子的对应节点,(用神奇倍增?)处理出这个东西之后沿着它下传对应节点即可。
上面这个东西我写了6k多,然后编译不过去。写法问题。然后重写了一遍,发现对应节点出问题。反正这个思路非常麻烦。复杂度为\(O(n+m\log^2 m)\)。
第二个是zwl的方法。我之前的那个是线段树套自动机,反过来不就行了!
对\(s\)串和所有\(t\)串建广义后缀自动机,记录\(s\)串的每个位置为结尾的子串在哪里,查询的时候倍增得到子串。广义后缀树上每个节点挂一个函数式线段树,记录每个\(t_i\)中这个串的出现次数,从下往上进行可持久化的线段树合并(新建非叶子节点),每次到对应的点的函数式线段树上查询即可。复杂度为\(O(n+m\log m)\)。
代码
这是超级好写的zwl方法。
#include<cstdio>
#include<cctype>
#include<cstring>
#include<utility>
#include<algorithm>
using namespace std;
int read() {
int x=0,f=1;
char c=getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
const int maxs=5e5+10;
const int maxm=5e4+10;
const int maxp=(maxs+maxm)<<1;
const int msgt=3e6+10;
const int maxj=20;
const int maxc=26;
char s[maxs],t[maxm];
int n,m,swhich[maxs];
typedef pair<int,int> Pair;
Pair operator + (Pair a,Pair b) {
if (a.second!=b.second) return a.second>b.second?a:b;
return a.first<b.first?a:b;
}
namespace sgt {
struct node {
int l,r;
Pair val;
} d[msgt];
int ids=0;
void inc(int &x,int l,int r,int p) {
if (!x) x=++ids;
if (l==r) {
d[x].val.first=l;
++d[x].val.second;
return;
}
int mid=(l+r)>>1;
p<=mid?inc(d[x].l,l,mid,p):inc(d[x].r,mid+1,r,p);
d[x].val=d[d[x].l].val+d[d[x].r].val;
}
void inc(int &x,int p) {
if (!p) return;
inc(x,1,m,p);
}
int merge(int x,int y,int l,int r) {
if (!x) return y;
if (!y) return x;
int nw=++ids,mid=(l+r)>>1;
if (l==r) {
d[nw].val=d[x].val;
d[nw].val.second+=d[y].val.second;
return nw;
}
d[nw].l=merge(d[x].l,d[y].l,l,mid);
d[nw].r=merge(d[x].r,d[y].r,mid+1,r);
d[nw].val=d[d[nw].l].val+d[d[nw].r].val;
return nw;
}
Pair query(int x,int L,int R,int l,int r) {
if (L==l && R==r) return d[x].val;
int mid=(L+R)>>1;
if (r<=mid) return query(d[x].l,L,mid,l,r);
if (l>mid) return query(d[x].r,mid+1,R,l,r);
return query(d[x].l,L,mid,l,mid)+query(d[x].r,mid+1,R,mid+1,r);
}
Pair query(int x,int l,int r) {
return query(x,1,m,l,r);
}
}
namespace sam {
int t[maxp][maxc],len[maxp],link[maxp],tot=1,last;
int root[maxp],f[maxp][maxj];
vector<int> g[maxp];
void reset() {last=1;}
int ext(int x,int id) {
if (t[last][x]) {
int p=t[last][x];
if (len[p]==len[last]+1) {
sgt::inc(root[p],id);
return last=p;
}
int q=++tot;
len[q]=len[last]+1;
memcpy(t[q],t[p],sizeof t[p]);
for (int j=last;j && t[j][x]==p;j=link[j]) t[j][x]=q;
link[q]=link[p],link[p]=q;
sgt::inc(root[q],id);
return last=q;
}
int nw=++tot,i;
sgt::inc(root[nw],id);
len[nw]=len[last]+1;
for (i=last;i && !t[i][x];i=link[i]) t[i][x]=nw;
if (i) {
int p=t[i][x];
if (len[p]==len[i]+1) link[nw]=p; else {
int q=++tot;
len[q]=len[i]+1;
memcpy(t[q],t[p],sizeof t[p]);
for (int j=i;j && t[j][x]==p;j=link[j]) t[j][x]=q;
link[q]=link[p],link[p]=link[nw]=q;
}
} else link[nw]=1;
return last=nw;
}
void add(int x,int y) {g[x].push_back(y);}
void dfs(int x,int fa) {
f[x][0]=fa;
for (int v:g[x]) {
dfs(v,x);
root[x]=sgt::merge(root[x],root[v],1,m);
}
}
void build() {
for (int i=2;i<=tot;++i) add(link[i],i);
dfs(1,1);
for (int j=1;j<maxj;++j) for (int i=1;i<=tot;++i) f[i][j]=f[f[i][j-1]][j-1];
}
int position(int l,int r) {
int x=swhich[r],chang=r-l+1;
for (int j=maxj-1;j>=0;--j) if (len[f[x][j]]>=chang) x=f[x][j];
return x;
}
Pair query(int tl,int tr,int p) {
return sgt::query(root[p],tl,tr);
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
#endif
scanf("%s",s+1),n=strlen(s+1);
m=read();
for (int i=1;i<=m;++i) {
scanf("%s",t);
int l=strlen(t);
sam::reset();
for (int j=0;j<l;++j) sam::ext(t[j]-'a',i);
}
sam::reset();
for (int i=1;i<=n;++i) swhich[i]=sam::ext(s[i]-'a',0);
sam::build();
int q=read();
while (q--) {
int tl=read(),tr=read(),sl=read(),sr=read();
int p=sam::position(sl,sr);
Pair ans=sam::query(tl,tr,p);
printf("%d %d\n",ans.second?ans.first:tl,ans.second);
}
return 0;
}