[NOI2015]品酒大会
[NOI2015]品酒大会
题目大意:
一个长度为\(n(n\le3\times10^5)\)的字符串\(s(s_i\in[\text{'a'},'z'])\),若对于后缀\(i,j\),\(\operatorname{lcp}(i,j)\le r\),则我们称后缀对\((i,j)\)为「\(r\)相似」的。另有数列\(a_{1\sim n}\),表示后缀对\((i,j)\)的贡献为\(a_i\cdot a_j\)。对于\(r=0,1,\ldots,n-1\),求「\(r\)相似」的后缀对有多少组,其中贡献的最大值为多少。
思路:
首先用后缀数组求出字符串\(s\)的\(lcp\)数组。对于\(lcp\)数组排序,按从大到小顺序合并\(lcp=i\)对应的两个后缀所属的联通块,合并后块内任意两个后缀的\(\operatorname{lcp}\)一定\(\ge r\),即一定为「\(r\)相似」的。对于每个连通块,维护\(a_i\)的最大值、次大值、最小值、次小值即可求得两数之积的最大值。对于方案数,可以简单地求出合并后各连通块内两两组合的方案数之和。
使用\(O(n\log^2n)\)的后缀数组可以在洛谷、LOJ、UOJ上通过,BZOJ会被卡,需要使用更优秀的算法。
源代码:
#include<cstdio>
#include<cctype>
#include<vector>
#include<climits>
#include<algorithm>
#include<functional>
typedef long long int64;
inline int getint() {
register char ch;
register bool neg=false;
while(!isdigit(ch=getchar())) neg|=ch=='-';
register int x=ch^'0';
while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
return neg?-x:x;
}
const int N=3e5+1;
char s[N];
int n,k,sa[N],rank[N],tmp[N];
inline bool cmp(const int &i,const int &j) {
if(rank[i]!=rank[j]) return rank[i]<rank[j];
const int ri=i+k<=n?rank[i+k]:-1;
const int rj=j+k<=n?rank[j+k]:-1;
return ri<rj;
}
inline void suffix_sort() {
for(register int i=0;i<=n;i++) {
sa[i]=i;
rank[i]=s[i];
}
for(k=1;k<=n;k<<=1) {
std::sort(&sa[0],&sa[n]+1,cmp);
tmp[sa[0]]=0;
for(register int i=1;i<=n;i++) {
tmp[sa[i]]=tmp[sa[i-1]]+cmp(sa[i-1],sa[i]);
}
std::copy(&tmp[0],&tmp[n]+1,rank);
}
}
struct Node {
int val,id;
bool operator > (const Node &rhs) const {
return val>rhs.val;
}
};
Node lcp[N];
inline void init_lcp() {
for(register int i=0,h=0;i<n;i++) {
if(h>0) h--;
const int &j=sa[rank[i]-1];
while(i+h<n&&j+h<n&&s[i+h]==s[j+h]) h++;
lcp[i]=(Node){h,i};
}
}
int m[2][2][N];
int64 ans[N],cnt[N];
struct DisjointSet {
int anc[N],size[N];
void reset(const int &n) {
for(register int i=1;i<=n;i++) {
anc[i]=i;
size[i]=1;
}
}
int find(const int &x) {
return x==anc[x]?x:anc[x]=find(anc[x]);
}
};
DisjointSet djs;
int main() {
n=getint();
scanf("%s",s);
for(register int i=1;i<=n;i++) {
m[0][0][i]=m[1][0][i]=getint();
m[0][1][i]=INT_MAX;
m[1][1][i]=INT_MIN;
}
suffix_sort();
init_lcp();
std::sort(&lcp[0],&lcp[n],std::greater<Node>());
djs.reset(n);
ans[n]=LLONG_MIN;
for(register int i=n-1,j=0;~i;i--) {
cnt[i]=cnt[i+1];
ans[i]=ans[i+1];
for(;j<n&&lcp[j].val>=i;j++) {
const int u=djs.find(lcp[j].id+1);
const int v=djs.find(sa[rank[lcp[j].id]-1]+1);
if(u==v||u==0||v==0) continue;
djs.anc[u]=v;
cnt[i]-=(int64)djs.size[u]*(djs.size[u]-1)/2+(int64)djs.size[v]*(djs.size[v]-1)/2;
djs.size[v]+=djs.size[u];
cnt[i]+=(int64)djs.size[v]*(djs.size[v]-1)/2;
for(register int i=0;i<2;i++) {
for(register int j=i;j<2;j++) {
if(m[0][i][u]<m[0][j][v]) std::swap(m[0][i][u],m[0][j][v]);
if(m[1][i][u]>m[1][j][v]) std::swap(m[1][i][u],m[1][j][v]);
}
}
ans[i]=std::max(ans[i],std::max((int64)m[0][0][v]*m[0][1][v],(int64)m[1][0][v]*m[1][1][v]));
}
}
for(register int i=0;i<n;i++) {
printf("%lld %lld\n",cnt[i],ans[i]==LLONG_MIN?0:ans[i]);
}
return 0;
}