题目描述
给一个字符串 S S S,有 Q Q Q次询问,每次询问给两个数 x , y x,y x,y,求 S S S的前缀 S [ 1 − > x ] S[1->x] S[1−>x]和 S S S的后缀 S [ ( n − y + 1 ) − > n ] S[(n-y+1)->n] S[(n−y+1)−>n]这两个字符串串联后得到的新串 T = S [ 1 − x ] + S [ ( n − y + 1 ) − > n ] T=S[1-x]+S[(n-y+1)->n] T=S[1−x]+S[(n−y+1)−>n],在 S S S中出现了多少次。
考试思路
昨天lsj大佬说今天有一道很好的字符串题
看完题面之后发现只有一道字符串
如果不是联赛,我肯定会往SAM\SA方向考虑
之前做过一个题,求一个字符串的每个前缀的出现次数
那个题是KMP,所以就先写了个KMP的模板
那个题的结论是对于一个前缀
i
i
i,所有i出现的位置
前缀
N
e
x
t
[
i
]
Next[i]
Next[i]都会出现,且不重复,证明类似于SAM
换句或说,一个前缀i的出现次数就是以i为Next的其他前缀的出现次数之和
然后考试时想到这就不会了,用个bitset存起来每个前缀和后缀出现的位置
PS:
考试时由于少上传了一组数据, n 2 n^2 n2的做法也能A,后来又重评了
正解
Step 1
其实就是我上面的那个结论
只不过这里把这个东西实体化了
可以发现,如果从i向Next[i]连一条边
那么会构成一棵树
并且对于一个节点,他的字数大小就是他的出现次数
于是我们就正反各建一棵树
注意,为了匹配方便,我们将后缀的树的节点编号都减一,这样对于一个位置,在两棵树上编号就一样了
Step 2
考虑有多少个位置符合条件
在前缀树上,肯定是x的子树节点之一
在后缀树上,肯定是y-1的子树节点之一
(为什么减一刚才说过了)
那么符合条件的节点就是既是x的子树中,有时y-1的子树中
把两棵树的Dfs序算出来
则j节点P符合要求的条件为
s
t
[
x
]
≤
d
f
n
[
P
]
≤
e
d
[
x
]
st[x]\leq dfn[P]\leq ed[x]
st[x]≤dfn[P]≤ed[x] 这是前缀树的DFS序
s
t
[
y
−
1
]
≤
d
f
n
[
p
−
1
]
≤
e
d
[
y
−
1
]
st[y-1]\leq dfn[p-1]\leq ed[y-1]
st[y−1]≤dfn[p−1]≤ed[y−1]这是后缀的DFS序
对于每个节点
如果把它在前缀树上的DFS序设为x,后缀树上的DFS设为y
那么每个节点都可以表示为二维平面上的一个点(x,y)
那么条件进一步转化为
求横坐标在
s
t
[
x
]
st[x]
st[x]到
e
d
[
x
]
ed[x]
ed[x]之间
纵坐标在
s
t
[
y
−
1
]
st[y-1]
st[y−1]到
e
d
[
y
−
1
]
ed[y-1]
ed[y−1]之间的点的个数
Step 3
上一部分的最终结论是一个经典的二维数点问题
这是模板题[SHOI2007]园丁的烦恼
其实可以转化为三维偏序或二维偏序
针对二维偏序
主流的大致做法有四种
1:CDQ分治(离线)
2:树状数组离散化(离线)
3:主席树(在线)
4:扫描线(离线)
主席树和扫描线细节比较多,而且想练一练CDQ分治,所以就选择了CDQ分治
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
struct node
{
int x,y,id,t;
}q[5*N],tmp[5*N];
struct edge
{
int next,y;
};
struct tree
{
edge e[2*N];
int link[N];
int t=0;
int st[N],ed[N];
int cnt=0;
void clear()
{
memset(e,0,sizeof(e));
memset(link,0,sizeof(link));
t=0;
memset(st,0,sizeof(st));
memset(ed,0,sizeof(ed));
cnt=0;
}
}auf,suf;
void add_auf(int x,int y)
{
auf.e[++auf.t].y=y;
auf.e[auf.t].next=auf.link[x];
auf.link[x]=auf.t;
}
void add_suf(int x,int y)
{
suf.e[++suf.t].y=y;
suf.e[suf.t].next=suf.link[x];
suf.link[x]=suf.t;
}
int tot=0,ans[N];
inline int read()
{
int X=0; bool flag=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
if(flag) return X;
return ~(X-1);
}
bool cmp(node a,node b)
{
if(a.x==b.x)
{
if(a.y==b.y) return a.t<b.t;
return a.y<b.y;
}
return a.x<b.x;
}
int n,m;
void Add(int x,int y,int t,int id)
{
q[++tot].id=id;
q[tot].x=x;
q[tot].y=y;
q[tot].t=t;
}
void CDQ(int l,int r)
{
if(l>=r) return;
int mid=(l+r)>>1;
CDQ(l,mid);
CDQ(mid+1,r);
int i=l,j=mid+1,len=0,top=0;
while(i<=mid&&j<=r)
{
if(q[i].y<=q[j].y)
{
if(q[i].t==0) len++;
tmp[++top]=q[i++];
}
else
{
if(q[j].t==2) ans[q[j].id]+=len;
if(q[j].t==1) ans[q[j].id]-=len;
tmp[++top]=q[j++];
}
}
while(i<=mid)
tmp[++top]=q[i++];
while(j<=r)
{
if(q[j].t==2) ans[q[j].id]+=len;
if(q[j].t==1) ans[q[j].id]-=len;
tmp[++top]=q[j++];
}
for(int i=1;i<=top;i++)
q[l+i-1]=tmp[i];
}
char a[N];
int Next[N],Mext[N];
void KMP()
{
Next[1]=0;
for(int i=2,j=0;i<=n;i++)
{
while(j>0&&a[i]!=a[j+1]) j=Next[j];
if(a[i]==a[j+1]) j++;
Next[i]=j;
}
Mext[n]=n+1;
for(int i=n-1,j=n+1;i>=1;i--)
{
while(j<n+1&&a[i]!=a[j-1]) j=Mext[j];
if(a[i]==a[j-1]) j--;
Mext[i]=j;
}
}
void dfs_auf(int x)
{
auf.st[x]=++auf.cnt;
for(int i=auf.link[x];i;i=auf.e[i].next)
{
int y=auf.e[i].y;
dfs_auf(y);
}
auf.ed[x]=auf.cnt;
}
void dfs_suf(int x)
{
suf.st[x]=++suf.cnt;
for(int i=suf.link[x];i;i=suf.e[i].next)
{
int y=suf.e[i].y;
dfs_suf(y);
}
suf.ed[x]=suf.cnt;
}
void Build()
{
for(int i=1;i<=n;i++)
{
add_auf(Next[i],i);
add_suf(Mext[i]-1,i-1);
}
dfs_auf(0);
dfs_suf(n);
}
int Q;
void clear()
{
memset(Next,0,sizeof(Next));
memset(Mext,0,sizeof(Mext));
memset(q,0,sizeof(q));
auf.clear();
suf.clear();
memset(ans,0,sizeof(ans));
tot=0;
}
void solve()
{
clear();
scanf("%d%d",&n,&Q);
scanf("%s",a+1);
KMP();
Build();
for(int i=0;i<=n;i++)
Add(auf.st[i],suf.st[i],0,0);
for(int i=1;i<=Q;i++)
{
int a,b,c,d;
int x=read(),y=read();
y=n-y;
a=auf.st[x];
c=auf.ed[x];
b=suf.st[y];
d=suf.ed[y];
Add(c,d,2,i);
Add(a-1,b-1,2,i);
Add(a-1,d,1,i);
Add(c,b-1,1,i);
}
sort(q+1,q+tot+1,cmp);
CDQ(1,tot);
for(int i=1;i<=Q;i++)
printf("%d\n",ans[i]);
}
int main()
{
freopen("e.in","r",stdin);
freopen("e.out","w",stdout);
int T;
cin>>T;
while(T--)
{
solve();
}
return 0;
}