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\)

数据范围

image

解题思路

后缀数组

考虑后缀数组中 \(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;
}
posted @ 2022-05-04 17:20  zyy2001  阅读(102)  评论(0编辑  收藏  举报