Foreword\text{Foreword}Foreword
人都道正难则反,我偏说正也不难。
这里介绍一种正面直接统计的做法。
和补集做法相比,没有那么多的分类讨论,更多的是对问题的正向分析和逐层化简、转化,也并不麻烦。
由于需要写很多线段树的操作,码量较大,本人在210行左右(但都是较为基础的操作,写起来很舒适)。
本来这就是一道码农题不是吗!
我们开始吧。
Solution\text{Solution}Solution
前置规定:
以下设截下的串的两端点(即题面描述 i+1,j−1i+1,j-1i+1,j−1)为 x,yx,yx,y,x,y∈(1,n)x,y\in(1,n)x,y∈(1,n)。
设询问串长度为 lenlenlen。
设 s(a,b)=∑i=abis(a,b)=\sum_{i=a}^bis(a,b)=∑i=abi,a>ba>ba>b 时,规定 s(a,b)=0s(a,b)=0s(a,b)=0。
(以下基本模拟我做本题的思维过程)
SA 和本题感觉不是很搭,那基本就是 SAM 了。
给的限制乍一看非常恶心,但细想想还挺有话说的。
Part 1
设询问串第一次出现的右端点 位置为 aaa,最后一次出现左端点 位置为 bbb,那么当中间截下的字符串 x>ax>ax>a 或 y<by<by<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≥bx\le a,y\ge bx≤a,y≥b 且包含询问串的字符串 (x,y)(x,y)(x,y) 对数。
这个东西并太不好做。
暴力怎么做?
暴力是不是我们固定一个 xxx,然后设左端点在 xxx 右侧的最靠左的字符串的右端点为 pospospos,那么 rrr 的取值范围就是 [max(b,pos),n−1][\max(b,pos),n-1][max(b,pos),n−1]。
随着左端点右移, pospospos 单调不升,右端点可以取到的范围肯定是逐渐变大,最后变成 [b,n−1][b,n-1][b,n−1]。
我们尝试掐头去尾的计算这部分的答案。
Part 2.1
我们找到满足 r≤br\le br≤b 的最靠左询问串,设其左端点为 uuu,那么当 x≤ux\le ux≤u 时,yyy 的取值范围就变成了 [b,n−1][b,n-1][b,n−1]。
这部分的贡献是:
(u−1)(n−b)(u-1)(n-b)(u−1)(n−b)
需要注意一些边界情况,如果 u≥au\ge au≥a,那么整个 part2 的贡献就是 (a−1)(n−b)(a-1)(n-b)(a−1)(n−b),加上后直接返回即可。
Part 2.2
设 l>al>al>a 的最靠左的询问串的左端点为 sufsufsuf,l≤al\le al≤a 的最靠右的询问串左端点为 preprepre,那么当 x∈(pre,a]x\in(pre,a]x∈(pre,a] 时,yyy 一直是在受 sufsufsuf 这个串约束,其范围是 [suf+len−1,n−1][suf+len-1,n-1][suf+len−1,n−1]。
这部分的贡献就是:
(a−pre)(n−suf)(a-pre)(n-suf)(a−pre)(n−suf)
Part 2.3
现在我们只剩下 x∈[u+1,pre]x\in[u+1,pre]x∈[u+1,pre] 这一段的贡献了。
我们想想最理想的情况:每个位置都有一个询问串的左端点,那么答案显然就是:
∑i=u+1pren−(i+len−1)=s(n−(pre+len−1),n−(u+1+len−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))
但是现实很骨感,真实答案可能会取不到这么多,那么会少多少呢?
举个栗子,设左端点出现集合为 101001101001101001,考虑它和 111111111111111111 相比少了多少答案。
不难发现,第一段长度为 111 的 000 串使答案减少了 111,第二段答案为 222 的 000 串使答案减少了 1+2=31+2=31+2=3。
一般的,一段长度为 LLL 的极长 000 串,会使答案减少 s(1,L)s(1,L)s(1,L)。
那么问题就变成求 [u+1,pre][u+1,pre][u+1,pre] 这部分 000 串减少的答案之和,利用线段树合并 startposstartposstartpos 集合即可轻松维护。
(startpos这个名字是我瞎起的,就是正常SAM的endpos)
然后本题就做完了。
时间复杂度 O((n+m)logn)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_JUDGEfreopen("a.in","r",stdin);freopen("a.out","w",stdout);
#endifn=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;
}
/*
*/