【题目】
原题地址
给定一个字符串 S S S,求 ∑ 1 ≤ i < j ≤ n l c p ( i , j ) l c s ( i , j ) [ l c p ( i , j ) ≤ k 1 ] [ l c s ( i , j ) ≤ k 2 ] \sum_{1\leq i<j\leq n}lcp(i,j)lcs(i,j)[lcp(i,j)\leq k_1][lcs(i,j)\leq k_2] 1≤i<j≤n∑lcp(i,j)lcs(i,j)[lcp(i,j)≤k1][lcs(i,j)≤k2]
其中 l c p ( i , j ) lcp(i,j) lcp(i,j)表示第 i i i个后缀和第 j j j个后缀的 l c p lcp lcp, l c s ( i , j ) lcs(i,j) lcs(i,j)表示第 i i i个前缀和第 j j j个前缀的 l c s lcs lcs。
∣ S ∣ ≤ 1 0 5 |S|\leq 10^5 ∣S∣≤105
【解题思路】
解法一:后缀数组
由于题目中定义的 l c p lcp lcp和 l c s lcs lcs是以一个点为中心往两边计算的,我们不妨将它们拼在一起。具体来说,假设我们知道 l c p ( i , j ) lcp(i,j) lcp(i,j)和 l c s ( i , j ) lcs(i,j) lcs(i,j),那么我们不难得到: S [ i − l c p ( i , j ) + 1 … i + l c s ( i , j ) − 1 ] = S [ j − l c p ( i , j ) + 1 … j + l c s ( i , j ) − 1 ] S[i-lcp(i,j)+1\dots i+lcs(i,j)-1]=S[j-lcp(i,j)+1\dots j+lcs(i,j)-1] S[i−lcp(i,j)+1…i+lcs(i,j)−1]=S[j−lcp(i,j)+1…j+lcs(i,j)−1],且 S [ i − l c p ( i , j ) ] ≠ S [ j − l c p ( i , j ) ] , S [ i + l c s ( i , j ) ] ≠ S [ j + l c s ( i , j ) ] S[i-lcp(i,j)]\neq S[j-lcp(i,j)],S[i+lcs(i,j)]\neq S[j+lcs(i,j)] S[i−lcp(i,j)]̸=S[j−lcp(i,j)],S[i+lcs(i,j)]̸=S[j+lcs(i,j)]。
于是我们对 S S S建出后缀数组,那么我们要考虑的就是每两个后缀之间的贡献。显然两个后缀 i , j i,j i,j有贡献当且仅当它们的 l c p lcp lcp不为 0 0 0且 S [ i − 1 ] ≠ S [ j − 1 ] S[i-1]\neq S[j-1] S[i−1]̸=S[j−1]。
这样做法就比较显然了:每个后缀 i i i要记录 S [ i − 1 ] S[i-1] S[i−1]是什么字符,按 height \text{height} height从大到小拆隔板进行启发式合并,考虑有多少对贡献的时候直接容斥一下,然后乘上长度为当前 height \text{height} height的贡献即可。
现在我们要求长度 l e n len len的串贡献是多少。
实际上它的贡献就是 ∑ i = 1 k 1 i ( l e n − i + 1 ) − ∑ i = 1 l e n − k 2 ( l e n − i + 1 ) \sum_{i=1}^{k_1}i(len-i+1)-\sum_{i=1}^{len-k_2}(len-i+1) ∑i=1k1i(len−i+1)−∑i=1len−k2(len−i+1)
(可能会有一些偏差以及边界问题,可以看代码)
这个东西可以预处理出来,这样我们就可以解决这道题了。
解法二: SAM + \text{SAM}+ SAM+边分树合并
我们对这个串的正序和逆序分别建出 SAM \text{SAM} SAM,考虑答案的计算。
根据 SAM \text{SAM} SAM的性质,两个前缀节点的 l c s lcs lcs就是这两个节点 l c a lca lca的 m x mx mx,后缀同理,那么如果把第一棵树大于 k 1 k_1 k1的节点置零,第二棵同理,问题就转化为了给定两棵树,询问所有点对 ( u , v ) (u,v) (u,v)在两棵树上 l c a lca lca的 m x mx mx的乘积之和。
考虑在第一棵树上枚举 l c a lca lca进行贡献,那么可以一个个合并 l c a lca lca的每个子树,考虑跨越两个连通块的点对在第二棵树上的 l c a lca lca的 m x mx mx值。
这个东西用边分树可以解决,具体点来讲我们将深度较低的一个联通块看成是右儿子而深度较大的一个联通块看成左儿子,然后每一个边分树节点维护一下左儿子中的节点个数和右儿子联通块中可能成为lca节点的点权和,然后合并的时候相乘一下就可以统计贡献了。
但这个东西我并不会写,照着别人的代码写,但发现重构树的时候我用自己的写法在合并答案的时候好像出现了一些小差错(WA了一个点),我并不知道为什么。希望有好心人来解释一下qwq。
【参考代码1】(SA)
#include
#define mkp make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
const int N=1e5+10;
int n,k1,k2;
int a[N],lp[N],rp[N],siz[N],bl[N],cnt[N][28];
char s[N];
pii h[N];
ull ans,f[N];
namespace SA
{
int rk[N],hi[N];
int wa[N],wb[N],wx[N],wy[N],sa[N];
bool cmp(int *r,int a,int b,int l){return r[a]==r[b] && r[a+l]==r[b+l];}
void getsa(int *r,int n,int m)
{
int *x=wa,*y=wb,*t,i,j,p;
for(i=0;i<m;++i) wx[i]=0;
for(i=0;i<n;++i) wx[x[i]=r[i]]++;
for(i=1;i<m;++i) wx[i]+=wx[i-1];
for(i=n-1;~i;--i) sa[--wx[x[i]]]=i;
for(j=1,p=0;p<n;j<<=1,m=p)
{
for(p=0,i=n-j;i<n;++i) y[p++]=i;
for(i=0;i<n;++i) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0;i<m;++i) wx[i]=0;
for(i=0;i<n;++i) wx[wy[i]=x[y[i]]]++;
for(i=1;i<m;++i) wx[i]+=wx[i-1];
for(i=n-1;~i;--i) sa[--wx[wy[i]]]=y[i];
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;++i)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
void getheight(int *r)
{
int i,j,k=0;
for(i=1;i<=n;++i) rk[sa[i]]=i;
for(i=0;i<n;hi[rk[i++]]=k)
for(k?k--:0,j=sa[rk[i]-1];r[i+k]==r[j+k];++k);
}
void adjust()
{
for(int i=1;i<=n;++i) sa[i]++;
for(int i=n;i;--i) rk[i]=rk[i-1];
sa[0]=rk[0]=0;
}
void output()
{
for(int i=0;i<=n;++i) printf("%d ",sa[i]); puts("");
for(int i=0;i<=n;++i) printf("%d ",rk[i]); puts("");
for(int i=0;i<=n;++i) printf("%d ",hi[i]); puts("");
}
}
using namespace SA;
ull sq(int x){return (ull)(x+1)*x/2;}
ull sq2(int x){return (ull)x*(2*x+1)*(x+1)/6;}
ull getf(int x)
{
if(x>=k1+k2)return 0;
ull s1=(ull)sq(min(x,k1))*(x+1)-sq2(min(x,k1));
ull s2=(ull)sq(max(0,x-k2))*(x+1)-sq2(max(0,x-k2));
//printf("%d %d %d %llu %llu\n",x,k1,k2,s1,s2);
return (ull)s1-s2;
}
void init()
{
scanf("%s%d%d",s,&k1,&k2);n=strlen(s);
k1=min(k1,n);k2=min(k2,n);
//printf("%llu %llu %llu\n",n,k1,k2);
for(int i=1;i<=n;++i) f[i]=getf(i);
//for(int i=1;i<=n;++i) printf("%llu\n",f[i]);
for(int i=0;i<n;++i) a[i]=s[i]-'a'+1;
getsa(a,n+1,30);getheight(a);adjust();//output();
for(int i=n;i;--i) a[i]=a[i-1]; a[0]=0;
//for(int i=1;i<=n;++i) printf("%d",a[i]);puts("");
for(int i=1;i<=n;++i) lp[i]=rp[i]=bl[i]=i,siz[i]=1,cnt[i][a[sa[i]-1]]++;
//for(int i=1;i<=n;++i) printf("%d %d\n",sa[i],a[sa[i]-1]);
for(int i=2;i<=n;++i) h[i-1]=mkp(-hi[i],i);
sort(h+1,h+n);
}
ull calc(int x,int y)
{
ull res=(ull)siz[x]*siz[y];
for(int i=1;i<=26;++i) res-=(ull)cnt[x][i]*cnt[y][i];
//printf("val:%d %d %llu\n",x,y,res);
return res;
}
void merge(int x,int y)
{
for(int i=1;i<=26;++i) cnt[y][i]+=cnt[x][i];
for(int i=lp[x];i<=rp[x];++i) bl[i]=y;
lp[y]=min(lp[y],lp[x]);rp[y]=max(rp[y],rp[x]);siz[y]+=siz[x];
}
void solve()
{
for(int i=1;i<n;++i)
{
int len=-h[i].fi,x=bl[h[i].se],y=bl[h[i].se-1];
if(siz[x]>siz[y]) swap(x,y);
//printf("val:%d merge:%d %d %d %d %d %d\n",len,x,lp[x],rp[x],y,lp[y],rp[y]);
ans+=(ull)f[len]*calc(x,y);merge(x,y);
}
printf("%llu",ans);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("LGP5115.in","r",stdin);
freopen("LGP5115.out","w",stdout);
#endif
init();solve();
return 0;
}
【参考代码2】(SAM+边分树合并)
#include
#define pb push_back
using namespace std;
typedef unsigned long long ull;
const int N=2e5+10,M=N<<1,INF=1e9;
int n,k1,k2;
ull ans;
char s[N];
namespace SAM
{
struct SAM
{
int rt,sz,las,mx[N],fa[N],pos[N],ch[N][26];
vector<int>son[N];
SAM(){rt=sz=las=1;}
int extend(int x)
{
int p,np,q,nq;
p=las;las=np=++sz;mx[np]=mx[p]+1;
for(;p && !ch[p][x];p=fa[p]) ch[p][x]=np;
if(!p) fa[np]=1;
else
{
q=ch[p][x];
if(mx[q]==mx[p]+1) fa[np]=q;
else
{
nq=++sz;mx[nq]=mx[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];fa[q]=fa[np]=nq;
for(;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
}
}
return las;
}
void init(char *s,int len)
{
for(int i=1;i<=len;++i) pos[i]=extend(s[i]-'a');
for(int i=1;i<=sz;++i) son[fa[i]].pb(i);
}
}S,T;
}
using namespace SAM;
namespace Graph
{
int tot,head[M];
struct Tway{int v,nex,w;}e[M<<1];
void Ginit(){tot=1;};
void add(int u,int v){e[++tot]=(Tway){v,head[u]};head[u]=tot;}
void addedge(int u,int v){add(u,v);add(v,u);}
}
using namespace Graph;
namespace Tree
{
int beg[M],en[M];
int sz,drt[M],ls[M],rs[M],val[M][22];
struct node{ull sl,sr;int lc,rc;}t[M*150];
void insert(int &x,int id,int pos,int d)
{
if(!id) return; x=++sz;
int tmp=val[pos][d]<=k2?val[pos][d]:0;
if(beg[id]<=beg[pos] && beg[pos]<=en[id])
t[x].sl=tmp,insert(t[x].lc,ls[id],pos,d+1);
else
t[x].sr=tmp,insert(t[x].rc,rs[id],pos,d+1);
}
int merge(int x,int y,ull td)
{
if(!x || !y) return x+y;
ans+=td*(t[x].sl*t[y].sr+t[x].sr*t[y].sl);
t[x].sl+=t[y].sl;t[x].sr+=t[y].sr;
t[x].lc=merge(t[x].lc,t[y].lc,td);
t[x].rc=merge(t[x].rc,t[y].rc,td);
return x;
}
}
using namespace Tree;
namespace Divide
{
int cnt,ind,sum,rt,rtfa,rtsz,RT;
int dep[M],siz[M];
deque<int>q;
void rebuild(int x)
{
val[x][0]=T.mx[x]; bool f=0;
for(int i=0;i<(int)T.son[x].size();++i)
{
val[++cnt][0]=T.mx[x];
addedge(!f?x:cnt-1,cnt);addedge(cnt,T.son[x][i]);f=1;
}
/*for(int i=0;i<(int)T.son[x].size();++i) q.pb(T.son[x][i]);
for(int u,v;q.size()>=2;)
{
u=q.front();q.pop_front();v=q.front();q.pop_front();
++cnt;val[cnt][0]=T.mx[x];
addedge(u,cnt);addedge(v,cnt);q.push_back(cnt);
}
for(;q.size();) addedge(x,q.front()),q.pop_front();*/
for(int i=0;i<(int)T.son[x].size();++i) rebuild(T.son[x][i]);
}//why use deque get wrong answer?
//I think they calc available contribution exactly the same,thouth the tree is dif
void dfsdep(int x,int fa,int d)
{
dep[x]=d;beg[x]=++ind;
for(int i=head[x];i;i=e[i].nex)
if(e[i].v^fa) dfsdep(e[i].v,x,d+1);
en[x]=ind;
}
void getval(int x,int fa,int tp,int d)
{
val[x][d]=abs(tp);
for(int i=head[x];i;i=e[i].nex)
if(e[i].v^fa && !e[i].w) getval(e[i].v,x,min(tp,val[e[i].v][0]),d);
}
void getroot(int x,int fa)
{
siz[x]=1;
for(int i=head[x];i;i=e[i].nex)
if(e[i].v^fa && !e[i].w) getroot(e[i].v,x),siz[x]+=siz[e[i].v];
int tsz=max(siz[x],sum-siz[x]);
if(tsz<rtsz) rt=x,rtfa=fa,rtsz=tsz;
}
void divide(int &x,int y,int d)
{
rt=rtfa=0;rtsz=INF;getroot(y,0);
if(dep[rt]<dep[rtfa]) swap(rt,rtfa),siz[rt]=sum-siz[rtfa];
x=rt;
int tsz=siz[x],tfa=rtfa,tsum=sum;
for(int i=head[rt];i;i=e[i].nex) if(e[i].v==rtfa) e[i].w=e[i^1].w=1;
getval(rt,0,-1,d);getval(rtfa,0,val[rtfa][0],d);
if(tsz>1) sum=tsz,divide(ls[x],x,d+1);
if(tsum-tsz>1) sum=tsum-tsz,divide(rs[x],tfa,d+1);
}
void dfs(int x)
{
for(int i=0;i<(int)S.son[x].size();++i)
dfs(S.son[x][i]),drt[x]=merge(drt[x],drt[S.son[x][i]],S.mx[x]<=k1?S.mx[x]:0);
}
}
using namespace Divide;
int main()
{
#ifndef ONLINE_JUDGE
freopen("LGP5115.in","r",stdin);
freopen("LGP5115.out","w",stdout);
#endif
scanf("%s%d%d",s+1,&k1,&k2);n=strlen(s+1);
S.init(s,n);reverse(s+1,s+n+1);T.init(s,n);
cnt=T.sz;Ginit();rebuild(T.rt);
dfsdep(T.rt,0,1);sum=cnt;divide(RT,1,1);
for(int i=1;i<=n;++i) insert(drt[S.pos[i]],RT,T.pos[n-i+1],1);
dfs(1);
printf("%llu\n",ans);
return 0;
}