NOI 2015 品酒大会
这题是SA+DSU的一道好题(我一开始想用FHQ Treap,不过DSU就可以维护,两种做法长度应该差不多)
首先我们要解决的第一个问题就是,数对太多(\(O(n^2)\)级别),我们没法使用任何东西去维护它。
观察样例\(1\),\((2,4),(2,9),(4,9)\)都是\(1\)相似的。从而我们发现一个重要的性质——如果\((a,b)\)是\(r\)相似的,\((b,c)\)是\(r\)相似的,那么\((a,c)\)也是\(r\)相似的。
那么此时我们就可以使用若干个数组\(A_{1},A_{2},...\)去维护这些数对,保证对于\(i,j\in A_{p},i\neq j\),满足\((i,j)\)是\(r\)相似的。比如,样例\(1\)中,\(r=1\)时,我们就有\((1,8),(2,4,9),(5,6,7,10),(3)\)这四个数组。并且方案数和是容易计算的,即为\(\sum \frac{|A_{i}|\times (|A_{i}|-1)}{2}\);最大乘积也是可以计算的,要取每个数组的前\(2\)大或前\(2\)小(负负得正)的值相乘即可。
此外,我们还可以发现,每当\(r\)增加时,一个数组会逐渐分裂成多个数组,还是样例\(1\)的例子,比如\(r=0\)时,只有一个数组\((1,2,3,...,10)\);当\(r=1\)时就是\((1,8),(2,4,9),(5,6,7,10),(3)\)这四个数组;当\(r=2\)时就是\((1,8),(4,9),(5,6),(2),(3),(7),(8)\)这七个数组了;而\(r\geq 3\),就会有\((1),(2),...,(10)\)这\(10\)个数组。
但是我们不好维护一个数组的分裂(因为一个数组可能会分裂成若干个数组),所以我们倒过来考虑这个问题,此时这个问题转成:
给你\(n\)个数组,第\(i\)个数组初始只有一个数\(i\),之后如果一个时刻\(r\),满足\((i,j)\)是\(r\)相似的,并且此时它们在不同的数组中,我们合并它们。
并且,我们需要维护数组的大小、数组前\(2\)大和前\(2\)小的值。
维护数组的大小、数组前\(2\)大和前\(2\)小的值,这个可以用并查集+set或FHQ Treap实现,由于一共只要合并\(O(n)\)次,所以时间复杂度就是\(O(n\log n)\)。
但难点在于我们怎么知道何时要合并两个数组。
我们可以考虑最早合并两个数,肯定是它们相同的连续段最长。那么如何知道 与某个后缀的 公共前缀 最长的 后缀 呢?肯定是后缀排序后的 前一项 或 后一项 啊!
(这里进行了人工断句)
所以我们将这个字符串进行后缀排序。那么两两之间合并的时间,一定是两个字符串间最早合并的时间,比如\((sa_i,sa_j)\)要在\(r\)时刻合并,且\(i,j\)不相邻,那么此时\((sa_i,sa_{i+1}),(sa_{i+1},sa_{i+2}),...,(sa_{j-1},sa_j)\)一定已经合并过了,所以我们只需关心相邻两项(指后缀排序过后相邻的)合并的时间即可。这个时间就是\(LCP(sa_i,sa_{i+1})=height_{i+1}\)。
所以,我们最终的算法,就是后缀排序,求出\(height\)数组,然后从\(n\)到\(0\)枚举时刻\(r\),看此时我们要将哪两个相邻的后缀(指后缀排序过后相邻的)合并,维护合并前后的数组即可。
求\(height\)数组是\(O(n)\)的,后缀排序是\(O(n\log n)\)的,故总时间复杂度为\(O(n\log n)\)。
代码:
#include<bits/stdc++.h>
#define debug(...) std::cerr<<#__VA_ARGS__<<" : "<<__VA_ARGS__<<std::endl
const int maxn=300005;
int n,m=122;
int a[maxn],c[maxn],x[maxn],y[maxn],sa[maxn],height[maxn];
char s[maxn];
std::vector<int> v[maxn];
long long sum=0;
long long ans1[maxn],ans2[maxn];
std::multiset<long long> mul;
class DSU { public:
int fa[maxn];
std::vector<int> mn[maxn],mx[maxn];
int root(int x) {
if(fa[x]<0) return x;
return fa[x]=root(fa[x]);
}
void sub(std::vector<int> &v,std::vector<int> u,bool sev) {
v.insert(v.end(),u.begin(),u.end());
if(!sev) std::sort(v.begin(),v.end(),std::less<int>());
else std::sort(v.begin(),v.end(),std::greater<int>());
if((int)v.size()>2) v.resize(2);
}
long long calc(int ind) {
long long ret=-1e18;
if(mn[ind].size()>=2) ret=std::max(ret,1ll*mn[ind][0]*mn[ind][1]);
if(mx[ind].size()>=2) ret=std::max(ret,1ll*mx[ind][0]*mx[ind][1]);
return ret;
}
void merge(int x,int y) {
int px=root(x),py=root(y);
if(px==py) return;
if(fa[px]>fa[py]) std::swap(px,py);
fa[px]+=fa[py]; fa[py]=px;
long long rec=calc(px);
if(mul.find(rec)!=mul.end()) mul.erase(mul.find(rec));
rec=calc(py);
if(mul.find(rec)!=mul.end()) mul.erase(mul.find(rec));
sub(mn[px],mn[py],0); sub(mx[px],mx[py],1);
rec=calc(px);
mul.insert(rec);
}
int size(int x) {
x=root(x); return -fa[x];
}
}solver;
#define ways(x) (((long long)(x))*((long long)(x)-1ll)/2ll)
int main() {
scanf("%d",&n); scanf("%s",s+1);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) c[x[i]=s[i]]++;
for(int i=1;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) sa[c[x[i]]--]=i;
for(int k=1;k<=n;k<<=1) {
int ptr=0;
for(int i=n-k+1;i<=n;i++) y[++ptr]=i;
for(int i=1;i<=n;i++) if(sa[i]>k) y[++ptr]=sa[i]-k;
memset(c,0,sizeof c);
for(int i=1;i<=n;i++) c[x[i]]++;
for(int i=1;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
std::swap(x,y);
x[sa[1]]=1,ptr=1;
for(int i=2;i<=n;i++) {
if(y[sa[i]]!=y[sa[i-1]]||y[sa[i]+k]!=y[sa[i-1]+k]) ptr++;
x[sa[i]]=ptr;
}
if(ptr==n) break;
m=ptr;
}
for(int i=1,k=0;i<=n;i++) {
if(!x[i]) continue;
if(k) k--;
while(s[i+k]==s[sa[x[i]-1]+k]) k++;
height[x[i]]=k;
}
for(int i=2;i<=n;i++)
v[height[i]].push_back(i);
for(int i=1;i<=n;i++) {
solver.fa[i]=-1;
solver.mn[i].push_back(a[sa[i]]);
solver.mx[i].push_back(a[sa[i]]);
}
for(int i=n;i>=0;i--) {
for(int j=0;j<(int)v[i].size();j++) {
int y=v[i][j],x=y-1;
sum-=ways(solver.size(x)); sum-=ways(solver.size(y));
solver.merge(x,y);
sum+=ways(solver.size(x));
}
ans1[i]=sum; if(!mul.empty()) ans2[i]=*mul.rbegin();
}
for(int i=0;i<n;i++) {
if(ans1[i]==0) printf("0 0\n");
else printf("%lld %lld\n",ans1[i],ans2[i]);
}
return 0;
}
浙公网安备 33010602011771号