Codeforces 666E Forensic Examination
题目大意:有一串原字符 S S , m m 个匹配字符串,问 S[pl,pr] S [ p l , p r ] 这段字符在,编号在 [l,r] [ l , r ] 之间的字符串中 出现次数最多的是哪一个,出现了多少次(最大次数相同则输出编号较小的)
理解在代码中
对于一大串字符的匹配,我们选择把 S S 和 m m 个字符串连起来(特殊符号连接)
然后我们就能发现,匹配上的字符串会在 right r i g h t 集合中出现
于是我们把 parent p a r e n t 树抽出来,因为 parent p a r e n t 对应的即是 right r i g h t 集合的树
然后把子串在 parent p a r e n t 树上的点都给标记对应的编号,用可持久化线段树记录
具体说一下可持久化线段树的结构:
以字符的位置(构建后缀自动机时是以一个一个字符为点讨论的)为更新的标准
然后以所属字符串编号为叶子节点的坐标建立线段树
然后用 parent p a r e n t 树的对应关系把它给合并起来(目的是方便统计)具体看代码解释
#include
#define ll long long
#define fo(i,j,k) for(i=j;i<=k;i++)
using namespace std;
const int M=24000005;
const int mxn=1200005;
char s[mxn];
int a,b,c,d,num,size,ans;
int cnt,m,T,p,q,np,nq,Q,tot,len;
int ls[M],rs[M],mx[M],id[M],B[mxn],C[mxn];
int color[mxn],pos[mxn],step[mxn],pre[mxn][24],son[mxn][28],root[mxn];
inline void update(int x)//更新操作
{
if(mx[ls[x]]==mx[x] && id[ls[x]]<id[x]) id[x]=id[ls[x]];
if(mx[ls[x]]>mx[x]) mx[x]=mx[ls[x]],id[x]=id[ls[x]];
if(mx[rs[x]]>mx[x]) mx[x]=mx[rs[x]],id[x]=id[rs[x]];
}
inline void insert(int &x,int l,int r,int v)//线段树插入操作
{
if(!x) x=(++size);
if(l==r) {mx[x]++,id[x]=l;return;}
int mid=l+r>>1;
if(v<=mid) insert(ls[x],l,mid,v);
else insert(rs[x],mid+1,r,v);
update(x);
}//这里并不是主席树的操作(没有继承上一个节点),是为了合并做准备(合并的节点并不是连续的)
inline void find(int x,int l,int r,int L,int R)//查询操作
{
if(!x) return;
if(L<=l && r<=R)
{
if(ansid[x];
else if(ans==mx[x] && num>id[x]) num=id[x];
return;
}
int mid=l+r>>1;
if(R<=mid) find(ls[x],l,mid,L,R);
else if(L>mid) find(rs[x],mid+1,r,L,R);
else find(ls[x],l,mid,L,mid),find(rs[x],mid+1,r,mid+1,R);
}
inline int merge(int x,int y,int l,int r)//合并
{
if(!x||!y) return x|y;//如果有一个节点不存在,就直接返回另一个几点
int z=(++size),mid=l+r>>1;//新建节点
if(l==r)
{
mx[z]=mx[x]+mx[y];//因为两个节点的对应字符串标号相同,所以直接将计数相加
id[z]=l;
return z;
}
//不一样则分别合并取最大
ls[z]=merge(ls[x],ls[y],l,mid);
rs[z]=merge(rs[x],rs[y],mid+1,r);
update(z);
return z;
}
inline void sam(int w)//建立后缀自动机,w为字符串标号,原字符串标号为0
{
int i,j,c;
fo(i,1,len+1)
{
p=np;
if(i==len+1) c=27;
else c=s[i]-'a'+1;
step[np=(++tot)]=step[p]+1;
if(!w && i<=len) pos[i]=np;//原字符串不加入线段树
if(w && i<=len) insert(root[np],1,cnt,w);//m个字符串加入线段树
while(p && !son[p][c])
son[p][c]=np,p=pre[p][0];//后面要在parent树上倍增,因此直接用pre[p][0]表示距离为1的父亲
if(!p) {pre[np][0]=1;continue;}
q=son[p][c];
if(step[q]==step[p]+1)
pre[np][0]=q;
else
{
step[nq=(++tot)]=step[p]+1;
memcpy(son[nq],son[q],sizeof son[q]);
pre[nq][0]=pre[q][0];
pre[q][0]=pre[np][0]=nq;
while(p && son[p][c]==q)
son[p][c]=nq,p=pre[p][0];
}
}
}
inline void init()
{
int i,j;
fo(i,1,tot) B[step[i]]++;
fo(i,1,tot) B[i]+=B[i-1];
fo(i,1,tot) C[B[step[i]]--]=i;
for(i=tot;i>=1;i--)
{
int x=C[i],fa=pre[x][0];//在parent树上找合并的节点
root[fa]=merge(root[fa],root[x],1,cnt);//合并节点->将当前的值累加到parent上
}
fo(j,1,22) fo(i,1,tot) pre[i][j]=pre[pre[i][j-1]][j-1];//parent树上倍增
}
int main()
{
int i,j;
scanf("%s",s+1);
len=strlen(s+1);
tot=np=1,sam(0);
scanf("%d",&cnt);
fo(i,1,cnt)
{
scanf("%s",s+1);
len=strlen(s+1);
sam(i);
}
init();
scanf("%d",&Q);
while(Q--)
{
scanf("%d%d%d%d",&c,&d,&a,&b);
len=b-a+1,b=pos[b];
for(j=22;j>=0;j--)
if(step[pre[b][j]]>=len)
b=pre[b][j];//通过倍增找到原字符串中的区间起点
ans=0,num=0,find(root[b],1,cnt,c,d);//寻找当前节点的答案
if(!ans) printf("%d %d\n",c,0);
else printf("%d %d\n",num,ans);
}
return 0;
}