NOI 2015 品酒大会
这题是SA+DSU的一道好题(我一开始想用FHQ Treap,不过DSU就可以维护,两种做法长度应该差不多)
首先我们要解决的第一个问题就是,数对太多(级别),我们没法使用任何东西去维护它。
观察样例,都是相似的。从而我们发现一个重要的性质——如果是相似的,是相似的,那么也是相似的。
那么此时我们就可以使用若干个数组去维护这些数对,保证对于,满足是相似的。比如,样例中,时,我们就有这四个数组。并且方案数和是容易计算的,即为;最大乘积也是可以计算的,要取每个数组的前大或前小(负负得正)的值相乘即可。
此外,我们还可以发现,每当增加时,一个数组会逐渐分裂成多个数组,还是样例的例子,比如时,只有一个数组;当时就是这四个数组;当时就是这七个数组了;而,就会有这个数组。
但是我们不好维护一个数组的分裂(因为一个数组可能会分裂成若干个数组),所以我们倒过来考虑这个问题,此时这个问题转成:
给你个数组,第个数组初始只有一个数,之后如果一个时刻,满足是相似的,并且此时它们在不同的数组中,我们合并它们。
并且,我们需要维护数组的大小、数组前大和前小的值。
维护数组的大小、数组前大和前小的值,这个可以用并查集+set或FHQ Treap实现,由于一共只要合并次,所以时间复杂度就是。
但难点在于我们怎么知道何时要合并两个数组。
我们可以考虑最早合并两个数,肯定是它们相同的连续段最长。那么如何知道 与某个后缀的 公共前缀 最长的 后缀 呢?肯定是后缀排序后的 前一项 或 后一项 啊!
(这里进行了人工断句)
所以我们将这个字符串进行后缀排序。那么两两之间合并的时间,一定是两个字符串间最早合并的时间,比如要在时刻合并,且不相邻,那么此时一定已经合并过了,所以我们只需关心相邻两项(指后缀排序过后相邻的)合并的时间即可。这个时间就是。
所以,我们最终的算法,就是后缀排序,求出数组,然后从到枚举时刻,看此时我们要将哪两个相邻的后缀(指后缀排序过后相邻的)合并,维护合并前后的数组即可。
求数组是的,后缀排序是的,故总时间复杂度为。
代码:
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话