1004. 品酒大会
题目链接
1004. 品酒大会
一年一度的“幻影阁夏日品酒大会”隆重开幕了。
大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个奖项,吸引了众多品酒师参 加。
在大会的晩餐上,调酒师 Rainbow 调制了 \(n\) 杯鸡尾酒。
这 \(n\) 杯鸡尾酒排成一行,其中第 \(i\) 杯酒 \((1 \leq i \leq n)\) 被贴上了一个标签 \(s_{i}\) ,每个标签都是 26 个小写英文字母之 -。
设 \(\operatorname{str}(l, r)\) 表示第 \(l\) 杯酒到第 \(r\) 杯酒的 \(r-l+1\) 个标签顺次连接构成的字符串。
若 \(\operatorname{str}\left(p, p_{0}\right)=\operatorname{str}\left(q, q_{0}\right)\) ,其中 \(1 \leq p \leq p_{0} \leq n, 1 \leq q \leq q_{0} \leq n, p \neq q, p_{0}-p+1=q_{0}-q+1=r\) ,则称第 \(p\) 杯酒与第 \(q\) 杯酒是 “ \(r\) 相似” 的。
特别地,对于任意的 \(1 \leq p, q \leq n, p \neq q\) ,第 \(p\) 杯酒和第 \(q\) 杯酒都是“ 0 相似”的。
在品尝环节上,品酒师 Freda 轻松地评定了每一杯酒的美味度,凭借其专业的水准和经验成功夺取了“首席品酒家” 的称号,其中第 \(i\) 杯酒 \((1 \leq i \leq n)\) 的美味度为 \(a_{i}\) 。
现在 Rainbow 公布了挑战环节的问题: 本次大会调制的鸡尾酒有一个特点,如果把第 \(p\) 杯酒与第 \(q\) 杯酒调兑在一 起,将得到一杯美味度为 \(a_{p} \times a_{q}\) 的酒。
现在请各位品酒师分别对于 \(r=0,1,2, \cdots, n-1\) ,统计出有多少种方法可以选出 \(2\) 杯 “ \(r\) 相似”的酒,并回答选 择 2 杯“ \(r\) 相似”的酒调兄可以得到的美味度的最大值。
输入格式
第 \(1\) 行包含 \(1\) 个正整数 \(n\) ,表示鸡尾酒的杯数。
第 \(2\) 行包含一个长度为 \(n\) 的字符串 \(S\) ,其中第 \(i\) 个字符表示第 \(i\) 杯酒的标签。
第 \(3\) 行包含 \(n\) 个整数,相邻整数之间用单个空格隔开,其中第 \(i\) 个整数表示第 \(i\) 杯酒的美味度 \(a_{i}\) 。
输出格式
输出共包括 \(n\) 行。
第 \(i\) 行输出 2 个整数,中间用单个空格隔开。
第 \(1\) 个整数表示选出两杯“ \((i-1)\) 相似”的酒的方案数,第 \(2\) 个整数表示选出两杯 “ \((i-1)\) 相似”的酒调兑可以得 到的最大美味度。
若不存在两杯 “ \((i-1)\) 相似” 的酒, 这两个数均为 \(0\) 。
数据范围
解题思路
后缀数组
考虑后缀数组中 \(height[i]\) 的定义:所有后缀中排名为 \(i\) 的后缀和排名为 \(i-1\) 的后缀的最长公共前缀长度,其中长度为子串的实际长度,不考虑填补的字符。而本题要求任意两个相等子串长度为 \(i\in [0,n-1]\) 的方案数,即对于要求的长度 \(i\),找出某一个长度为 \(i\) 的子串的数量 \(cnt\),其
贡献为 \(C_{cnt}^2\),累加所有这样的子串贡献即可
\(\color{red}{现在关键在于如何将其转化为\ height\ 数组?}\)
由于所有的子串都是所有后缀的前缀,可以考虑按长度从大到小计算,从相同长度为 \(n-1\) 开始,此时仅有两个长度为 \(n-1\) 的子串,如果存在相等的话,这两个排名一定是相邻的,因为两个子串除了最后一个字符完全相等,这时可用并查集维护相同长度的信息,即相同长度的个数,同时计算答案,相同长度递减为 \(i\) 时,同理,只需要合并那些相邻相同长度为 \(i\) 的后缀,而此时不会存在某两个子串相同长度为 \(i\) 却没有统计到的情况,\(\color{red}{为什么?}\)考虑任意一个子串对应的后缀所在的排名,假设其比另外一个子串对应的后缀的排名要靠前,而其两后缀的前缀长度最长,即最靠近,即相邻,而这时将这些后缀合并正好统计到这些数量,而相同长的子串会对相同长度短的子串有影响,所以需要按长度从大到小计算,因为每次合并都是合并相邻排名的后缀,当两后缀集合不是同一个集合时,由于是按长度从大到小计算,所以两集合后缀的最长前缀不会小于当前相同长度,合并的同时更新答案,另外还需要计算任意两个子串的权值最大乘积,按正负性,无非就三种情况:\(正\times 正,负\times 负,正\times 负\),即统计 \(最大值,次大值,最小值,次小值\),最后答案为 \(max(最大值\times 次大值,最小值\times 次小值)\),同样可以用并查集维护这些信息
- 时间复杂度:\(O(nlogn)\)
代码
// Problem: 品酒大会
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/1006/
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=3e5+5,inf=0x3f3f3f3f;
int n,m,a[N],sa[N],height[N],rk[N],c[N],x[N],y[N];
char s[N];
PLL res[N];
vector<int> b[N];
int fa[N],sz[N];
LL max1[N],max2[N],min1[N],min2[N];
void get_sa()
{
for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
for(int i=2;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 cnt=0;
for(int i=n-k+1;i<=n;i++)y[++cnt]=i;
for(int i=1;i<=n;i++)
if(sa[i]>k)y[++cnt]=sa[i]-k;
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[i]]++;
for(int i=2;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;
swap(x,y);
x[sa[1]]=1,cnt=1;
for(int i=2;i<=n;i++)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?cnt:++cnt;
if(cnt==n)break;
m=cnt;
}
}
void get_height()
{
for(int i=1;i<=n;i++)rk[sa[i]]=i;
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==1)continue;
if(k)k--;
int j=sa[rk[i]-1];
while(i+k<=n&&s[i+k]==s[j+k])k++;
height[rk[i]]=k;
}
}
LL C(int x)
{
return x*(x-1ll)/2;
}
int find(int x)
{
return x==fa[x]?x:fa[x]=find(fa[x]);
}
PLL cal(int r)
{
static LL cnt=0,mx=LONG_LONG_MIN;
for(int x:b[r])
{
int a=find(x),b=find(x-1);
if(a==b)continue;
cnt-=C(sz[a]);
cnt-=C(sz[b]);
fa[a]=b;
sz[b]+=sz[a];
cnt+=C(sz[b]);
if(max1[b]<max1[a])
{
max2[b]=max(max1[b],max2[a]);
max1[b]=max1[a];
}
else
max2[b]=max(max2[b],max1[a]);
if(min1[b]>min1[a])
{
min2[b]=min(min1[b],min2[a]);
min1[b]=min1[a];
}
else
min2[b]=min(min2[b],min1[a]);
mx=max({mx,max1[b]*max2[b],min1[b]*min2[b]});
}
if(cnt==0)return {0,0};
return {cnt,mx};
}
int main()
{
scanf("%d",&n);
scanf("%s",s+1);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
m='z';
get_sa();
get_height();
for(int i=1;i<=n;i++)
{
fa[i]=i,sz[i]=1;
min1[i]=max1[i]=a[sa[i]];
min2[i]=inf,max2[i]=-inf;
}
for(int i=2;i<=n;i++)b[height[i]].pb(i);
for(int i=n-1;i>=0;i--)res[i]=cal(i);
for(int i=0;i<n;i++)printf("%lld %lld\n",res[i].fi,res[i].se);
return 0;
}