P2178 [NOI2015] 品酒大会 题解
P2178 [NOI2015] 品酒大会
题面啰唆,实际上所谓 “\(p\) 和 \(q\) 是 \(r\) 相似” 的意思就是以 \(p\) 开头的后缀和以 \(q\) 开头的后缀的前 \(r\) 个字符都是相等的。所以,\(\operatorname{LCP}(p,q)=r\) 实际上等价于 \(p\) 和 \(q\) 是 \(1\) 相似、\(2\) 相似、……一直到 \(r\) 相似。
那这个问题就是后缀数组的典型应用:求后缀 LCP 所带来的贡献,在此之前同类型的一道题是 P4248 [AHOI2013] 差异。求出 \(\rm sa\) 和 \(\rm height\) 后,我们采用单调栈求出向左和向右第一个小于 \(\text{height}(i)\) 的 \(\text{height}(j)\) 的位置分别记作 \(l_i\) 和 \(r_i\),那么 \((l_i,r_i)\) 这段区间就是 \(i\) 作为区间最小值的区间,也就是它能提供贡献的区间。那么第一问的答案就是 \((i-l_i)\times(r_i-i)\),这和上面那道题是一样的。然后考虑求解第二问,同理因为提供贡献的区间还是在 \((l_i,r_i)\) 这个范围内,所以要用 ST 表维护区间最大值和最小值(有可能负负得正),就得到当前点第二问的答案。
特殊地,因为题目中的定义,\(i\) 相似的答案同时也是 \(i-1\) 相似的答案,所以需要从后往前将第一问答案求和,第二问答案求最大值,然后才能输出。
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
constexpr int MAXN=3e5+5;
constexpr ll INF=0xc0c0c0c0c0c0c0c0ll;
int n;
ll a[MAXN];
string s;
int sa[MAXN],rk[MAXN],rk2[MAXN],id[MAXN],cnt[MAXN];
int h[MAXN],f[MAXN][21],g[MAXN][21];
ll ans[MAXN],res[MAXN];
int stk[MAXN],top,l[MAXN],r[MAXN];
void getsa(int m){
for(int i=1;i<=n;i++) cnt[rk[i]=s[i]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i;i--) sa[cnt[rk[i]]--]=i;
for(int w=1,p,cur;;w<<=1,m=p){
cur=0;
for(int i=n-w+1;i<=n;i++) id[++cur]=i;
for(int i=1;i<=n;i++) if(sa[i]>w) id[++cur]=sa[i]-w;
memset(cnt,0,(m+1)<<2);
for(int i=1;i<=n;i++) cnt[rk[i]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i;i--) sa[cnt[rk[id[i]]]--]=id[i];
p=0;
memcpy(rk2,rk,(n+1)<<2);
for(int i=1;i<=n;i++)
rk[sa[i]]=rk2[sa[i]]==rk2[sa[i-1]]&&rk2[sa[i]+w]==rk2[sa[i-1]+w]?p:++p;
if(p==n) break;
}
}
void geth(){
for(int i=1,k=0;i<=n;i++){
if(!rk[i]) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
h[rk[i]]=k;
}
}
void ST(){
for(int i=1;i<=n;i++) f[i][0]=g[i][0]=a[sa[i]];
for(int j=1;j<=20;j++)
for(int i=1;i+(1<<j)-1<=n;i++){
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
g[i][j]=max(g[i][j-1],g[i+(1<<(j-1))][j-1]);
}
}
ll amn(int l,int r){
int s=__lg(r-l+1);
return min(f[l][s],f[r-(1<<s)+1][s]);
}
ll amx(int l,int r){
int s=__lg(r-l+1);
return max(g[l][s],g[r-(1<<s)+1][s]);
}
int main(){
cin.tie(nullptr)->sync_with_stdio(0);
cin>>n>>s;
s=' '+s;
getsa('z'),geth();
for(int i=1;i<=n;i++) cin>>a[i];
ST();
memset(res,0xc0,(n+1)<<3);
for(int i=1;i<=n;i++){
while(top&&h[stk[top]]>h[i]) top--;
l[i]=stk[top];
stk[++top]=i;
}
stk[top=1]=n+1;
h[n+1]=-1;
for(int i=n;i;i--){
while(top&&h[stk[top]]>=h[i]) top--;
r[i]=stk[top];
stk[++top]=i;
}
for(int i=2;i<=n;i++){
ans[h[i]]+=1ll*(i-l[i])*(r[i]-i);
res[h[i]]=max({res[h[i]],amn(l[i],i-1)*amn(i,r[i]-1),amx(l[i],i-1)*amx(i,r[i]-1)});
}
for(int i=n-2;~i;i--){
ans[i]+=ans[i+1];
res[i]=max(res[i],res[i+1]);
}
for(int i=0;i<n;i++) cout<<ans[i]<<' '<<(res[i]==INF?0:res[i])<<'\n';
return 0;
}
即得易见平凡,仿照上例显然。留作习题答案略,读者自证不难。
反之亦然同理,推论自然成立。略去过程 $\rm QED$,由上可知证毕。

浙公网安备 33010602011771号