Foreword \text{Foreword} Foreword
人都道正难则反,我偏说正也不难。
这里介绍一种正面直接统计的做法。
和补集做法相比,没有那么多的分类讨论,更多的是对问题的正向分析和逐层化简、转化,也并不麻烦。
由于需要写很多线段树的操作,码量较大,本人在210行左右(但都是较为基础的操作,写起来很舒适)。
本来这就是一道码农题不是吗!
我们开始吧。
Solution \text{Solution} Solution
前置规定:
以下设截下的串的两端点(即题面描述
i
+
1
,
j
−
1
i+1,j-1
i+1,j−1)为
x
,
y
x,y
x,y,
x
,
y
∈
(
1
,
n
)
x,y\in(1,n)
x,y∈(1,n)。
设询问串长度为
l
e
n
len
len。
设
s
(
a
,
b
)
=
∑
i
=
a
b
i
s(a,b)=\sum_{i=a}^bi
s(a,b)=∑i=abi,
a
>
b
a>b
a>b 时,规定
s
(
a
,
b
)
=
0
s(a,b)=0
s(a,b)=0。
(以下基本模拟我做本题的思维过程)
SA 和本题感觉不是很搭,那基本就是 SAM 了。
给的限制乍一看非常恶心,但细想想还挺有话说的。
Part 1
设询问串第一次出现的右端点 位置为
a
a
a,最后一次出现左端点 位置为
b
b
b,那么当中间截下的字符串
x
>
a
x>a
x>a 或
y
<
b
y<b
y<b 的时候,必然是合法的。
SAM 反串先建出后缀树,记录出现的最早和最晚位置并向父亲传递,即可维护这两个值。
容易统计这个时候的答案,应该是:
s
(
1
,
n
−
a
−
1
)
+
s
(
1
,
b
−
2
)
−
s
(
1
,
(
b
−
1
)
−
(
a
+
1
)
+
1
)
s(1,n-a-1)+s(1,b-2)-s(1,(b-1)-(a+1)+1)
s(1,n−a−1)+s(1,b−2)−s(1,(b−1)−(a+1)+1)
最后减去的是为了容斥掉左右端点同时满足条件算重的方案数。
Part 2
那么现在的问题就转化为了:
求 x ≤ a , y ≥ b x\le a,y\ge b x≤a,y≥b 且包含询问串的字符串 ( x , y ) (x,y) (x,y) 对数。
这个东西并太不好做。
暴力怎么做?
暴力是不是我们固定一个
x
x
x,然后设左端点在
x
x
x 右侧的最靠左的字符串的右端点为
p
o
s
pos
pos,那么
r
r
r 的取值范围就是
[
max
(
b
,
p
o
s
)
,
n
−
1
]
[\max(b,pos),n-1]
[max(b,pos),n−1]。
随着左端点右移,
p
o
s
pos
pos 单调不升,右端点可以取到的范围肯定是逐渐变大,最后变成
[
b
,
n
−
1
]
[b,n-1]
[b,n−1]。
我们尝试掐头去尾的计算这部分的答案。
Part 2.1
我们找到满足
r
≤
b
r\le b
r≤b 的最靠左询问串,设其左端点为
u
u
u,那么当
x
≤
u
x\le u
x≤u 时,
y
y
y 的取值范围就变成了
[
b
,
n
−
1
]
[b,n-1]
[b,n−1]。
这部分的贡献是:
(
u
−
1
)
(
n
−
b
)
(u-1)(n-b)
(u−1)(n−b)
需要注意一些边界情况,如果
u
≥
a
u\ge a
u≥a,那么整个 part2 的贡献就是
(
a
−
1
)
(
n
−
b
)
(a-1)(n-b)
(a−1)(n−b),加上后直接返回即可。
Part 2.2
设
l
>
a
l>a
l>a 的最靠左的询问串的左端点为
s
u
f
suf
suf,
l
≤
a
l\le a
l≤a 的最靠右的询问串左端点为
p
r
e
pre
pre,那么当
x
∈
(
p
r
e
,
a
]
x\in(pre,a]
x∈(pre,a] 时,
y
y
y 一直是在受
s
u
f
suf
suf 这个串约束,其范围是
[
s
u
f
+
l
e
n
−
1
,
n
−
1
]
[suf+len-1,n-1]
[suf+len−1,n−1]。
这部分的贡献就是:
(
a
−
p
r
e
)
(
n
−
s
u
f
)
(a-pre)(n-suf)
(a−pre)(n−suf)
Part 2.3
现在我们只剩下 x ∈ [ u + 1 , p r e ] x\in[u+1,pre] x∈[u+1,pre] 这一段的贡献了。
我们想想最理想的情况:每个位置都有一个询问串的左端点,那么答案显然就是:
∑
i
=
u
+
1
p
r
e
n
−
(
i
+
l
e
n
−
1
)
=
s
(
n
−
(
p
r
e
+
l
e
n
−
1
)
,
n
−
(
u
+
1
+
l
e
n
−
1
)
)
\sum_{i=u+1}^{pre}n-(i+len-1)=s(n-(pre+len-1),n-(u+1+len-1))
i=u+1∑pren−(i+len−1)=s(n−(pre+len−1),n−(u+1+len−1))
但是现实很骨感,真实答案可能会取不到这么多,那么会少多少呢?
举个栗子,设左端点出现集合为
101001
101001
101001,考虑它和
111111
111111
111111 相比少了多少答案。
不难发现,第一段长度为
1
1
1 的
0
0
0 串使答案减少了
1
1
1,第二段答案为
2
2
2 的
0
0
0 串使答案减少了
1
+
2
=
3
1+2=3
1+2=3。
一般的,一段长度为
L
L
L 的极长
0
0
0 串,会使答案减少
s
(
1
,
L
)
s(1,L)
s(1,L)。
那么问题就变成求
[
u
+
1
,
p
r
e
]
[u+1,pre]
[u+1,pre] 这部分
0
0
0 串减少的答案之和,利用线段树合并
s
t
a
r
t
p
o
s
startpos
startpos 集合即可轻松维护。
(startpos这个名字是我瞎起的,就是正常SAM的endpos)
然后本题就做完了。
时间复杂度
O
(
(
n
+
m
)
log
n
)
O((n+m)\log n)
O((n+m)logn)。
Code \text{Code} Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
using namespace std;
const int N=4e5+100;
const int C=10;
inline ll read(){
ll x(0),f(1);char c=getchar();
while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}
while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
int n,m,k;
int tag[N],mn[N],mx[N];
int pos[N];
int pl[N][20];
vector<int>v[N];
#define calc(x) (1ll*(x)*((x)+1)/2)
struct node{
int llen,rlen,siz;
ll sum;
};
node operator + (const node &a,const node &b){
node o;
o.siz=a.siz+b.siz;
o.llen=a.llen==a.siz?a.siz+b.llen:a.llen;
o.rlen=b.rlen==b.siz?b.siz+a.rlen:b.rlen;
o.sum=a.sum+b.sum;
o.sum+=calc(a.rlen+b.llen)-calc(a.rlen)-calc(b.llen);
return o;
}
inline node emp(int len){
return (node){len,len,len,calc(len)};
}
struct tree{
int ls,rs;
node o;
};
int rt[N],tot;
struct Seg{
tree tr[N*20];
#define mid ((l+r)>>1)
inline void pushup(int k,int l,int r){
node x=tr[k].ls?tr[tr[k].ls].o:emp(mid-l+1);
node y=tr[k].rs?tr[tr[k].rs].o:emp(r-mid);
tr[k].o=x+y;
return;
}
inline int copy(int x){
tr[++tot]=tr[x];
return tot;
}
void upd(int &k,int l,int r,int p){
k=copy(k);
if(l==r){
tr[k].o=(node){0,0,1,0};
return;
}
if(p<=mid) upd(tr[k].ls,l,mid,p);
else upd(tr[k].rs,mid+1,r,p);
pushup(k,l,r);
}
int merge(int x,int y,int l,int r){
if(!x||!y) return x|y;
int now=copy(x);
tr[now].ls=merge(tr[now].ls,tr[y].ls,l,mid);
tr[now].rs=merge(tr[now].rs,tr[y].rs,mid+1,r);
pushup(now,l,r);
return now;
}
int findsuf(int k,int l,int r,int p){
if(!k) return 0;
if(l==r) return (l>=p)?l:0;
if(p>mid) return findsuf(tr[k].rs,mid+1,r,p);
else{
int res=findsuf(tr[k].ls,l,mid,p);
if(res) return res;
else return findsuf(tr[k].rs,mid+1,r,p);
}
}
int findpre(int k,int l,int r,int p){
if(!k) return 0;
if(l==r) return (l<=p)?l:0;
if(p<=mid) return findpre(tr[k].ls,l,mid,p);
else{
int res=findpre(tr[k].rs,mid+1,r,p);
if(res) return res;
else return findpre(tr[k].ls,l,mid,p);
}
}
node query(int k,int l,int r,int x,int y){
if(!k) return emp(min(r,y)-max(l,x)+1);
if(x<=l&&r<=y) return tr[k].o;
if(y<=mid) return query(tr[k].ls,l,mid,x,y);
else if(x>mid) return query(tr[k].rs,mid+1,r,x,y);
else return query(tr[k].ls,l,mid,x,y)+query(tr[k].rs,mid+1,r,x,y);
}
}seg;
void dfs(int x,int fa){
pl[x][0]=fa;
for(int k=1;pl[x][k-1];k++) pl[x][k]=pl[pl[x][k-1]][k-1];
for(int to:v[x]){
dfs(to,x);
mn[x]=min(mn[x],mn[to]);
mx[x]=max(mx[x],mx[to]);
rt[x]=seg.merge(rt[x],rt[to],1,n);
}
if(tag[x]){
seg.upd(rt[x],1,n,tag[x]);
}
return;
}
struct SAM{
int tr[N][C],fa[N],len[N],tot,lst;
SAM(){
tot=lst=1;
}
inline void ins(int c,int id){
c-='0';
int cur=++tot,p=lst;lst=tot;
len[cur]=len[p]+1;
pos[id]=cur;
tag[cur]=id;mn[cur]=mx[cur]=id;
for(;p&&!tr[p][c];p=fa[p]) tr[p][c]=cur;
if(!p) fa[cur]=1;
else{
int q=tr[p][c];
if(len[q]==len[p]+1) fa[cur]=q;
else{
int nq=++tot;
mn[nq]=n+1;mx[nq]=0;
len[nq]=len[p]+1;fa[nq]=fa[q];
for(int i=0;i<C;i++) tr[nq][i]=tr[q][i];
fa[cur]=fa[q]=nq;
for(;p&&tr[p][c]==q;p=fa[p]) tr[p][c]=nq;
}
}
return;
}
void build(){
for(int i=2;i<=tot;i++) v[fa[i]].push_back(i);
dfs(1,0);
}
int find(int l,int r){
int x=pos[l],L=r-l+1;
for(int k=18;k>=0;k--){
if(len[pl[x][k]]>=L) x=pl[x][k];
}
return x;
}
}sam;
inline ll Sum(ll x,ll y){
if(x>y) return 0;
else return (x+y)*(y-x+1)/2;
}
ll work(int l,int r){
int x=sam.find(l,r),len=r-l+1;
int a=mn[x]+len-1,b=mx[x];
ll ans=Sum(1,n-a-1)+Sum(1,b-2);
int w=(b-1)-(a+1)+1;
if(w>=1) ans-=calc(w);
a=min(a,b);
int pre=seg.findpre(rt[x],1,n,a),suf=seg.findsuf(rt[x],1,n,a+1);
int u=seg.findpre(rt[x],1,n,b-len+1);
if(u>=a){
ans+=1ll*(a-1)*(n-b);
return ans;
}
if(suf){
ans+=1ll*(a-pre)*(n-(suf+len-1));
}
if(u) ans+=1ll*(u-1)*(n-b);
//calc: [u+1,pre]
u=max(u,1);
if(u+1<=pre){
ans+=Sum(n-(pre+len-1),n-(u+1+len-1));
node o=seg.query(rt[x],1,n,u+1,pre);
ans-=o.sum;
}
return ans;
}
char s[N];
signed main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
#endif
n=read();m=read();
scanf(" %s",s+1);
for(int i=n;i>=1;i--) sam.ins(s[i],i);
sam.build();
for(int i=1;i<=m;i++){
int l=read(),r=read();
printf("%lld\n",work(l,r));
}
return 0;
}
/*
*/