品酒大会

品酒大会

时间限制:1s 内存限制 512MB
题目
一年一度的“幻影阁夏日品酒大会”隆重开幕了。大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个奖项,吸引了众多品酒师参加。
在大会的晚餐上,调酒师 Rainbow 调制了 n 杯鸡尾酒。这 n 杯鸡尾酒排成一行,其中第 i 杯酒 (1≤i≤n) 被贴上了一个标签 si,每个标签都是 26 个小写英文字母之一。设 Str(l,r) 表示第 l 杯酒到第 r 杯酒的 r−l+1 个标签顺次连接构成的字符串。若 Str(p,po)=Str(q,qo),其中 1≤p≤po≤n,1≤q≤qo≤n,p≠q,po−p+1=qo−q+1=r,则称第 p 杯酒与第 q 杯酒是“r相似” 的。当然两杯“r相似” (r>1)的酒同时也是“1 相似”、“2 相似”、…、“(r−1) 相似”的。特别地,对于任意的 1≤p,q≤n,p≠q,第 p 杯酒和第 q 杯酒都是“0相似”的。
在品尝环节上,品酒师 Freda 轻松地评定了每一杯酒的美味度,凭借其专业的水准和经验成功夺取了“首席品酒家”的称号,其中第 i 杯酒 (1≤i≤n) 的美味度为 ai。现在 Rainbow 公布了挑战环节的问题:本次大会调制的鸡尾酒有一个特点,如果把第 p 杯酒与第 q 杯酒调兑在一起,将得到一杯美味度为 apaq 的酒。现在请各位品酒师分别对于 r=0,1,2,…,n−1,统计出有多少种方法可以选出 2 杯“r相似”的酒,并回答选择 2 杯“r相似”的酒调兑可以得到的美味度的最大值。

数据范围:n<=300000,|ai|<=1000000000

来源
noi2015day2t2

题解

用后缀数组处理出height,然后按照height从大到小排序,每次将i和i-1用并查集合并,并维护每个连通块的最大值,最小值(有负数),和组数即可。

代码

#include<iostream> 
#include<cstdio> 
#include<cstdlib> 
#include<cstring> 
#include<string> 
#include<cmath> 
#include<algorithm>
#define N 300010
#define ll long long
using namespace std;
int n,m,val[N],fa[N],size[N],maxn[N],minn[N];
int x[N],y[N],t[N],cnt[N],sa[N],height[N],rank[N];
ll ans[N],sum[N];char s[N];
bool cmp(int *g,int a,int b,int l)
{return g[a]==g[b]&&g[a+l]==g[b+l];}
bool cmp1(const int &x,const int &y)
{return height[x]>height[y];}

void get_sa()
{
  for(int i=1;i<=n;i++)cnt[x[i]=s[i]]++;
  for(int i=2;i<=m;i++)cnt[i]+=cnt[i-1];
  for(int i=n;i;i--)sa[cnt[s[i]]--]=i;
  for(int j=1,tot=0;tot<n;j<<=1,m=tot)
  {
    tot=0;for(int i=n-j+1;i<=n;i++)y[++tot]=i;
    for(int i=1;i<=n;i++)if(sa[i]>j)y[++tot]=sa[i]-j;
    for(int i=1;i<=n;i++)t[i]=x[y[i]];
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)cnt[t[i]]++;
    for(int i=2;i<=m;i++)cnt[i]+=cnt[i-1];
    for(int i=n;i;i--)sa[cnt[t[i]]--]=y[i];
    for(int i=1;i<=n;i++)swap(x[i],y[i]);
    tot=2;x[sa[1]]=1;
    for(int i=2;i<=n;i++)
      x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?tot-1:tot++;
  }
}

void get_height()
{
  for(int i=1;i<=n;i++)rank[sa[i]]=i;
  for(int i=1,j,k=0;i<=n;height[rank[i++]]=k)
    for(k?k--:0,j=sa[rank[i]-1];s[i+k]==s[j+k];k++);
}

int find(int x)
{
  if(fa[x]==x)return x;
  return fa[x]=find(fa[x]);
}

void solve()
{
  for(int i=1;i<=n;i++)
    t[i]=i,fa[i]=i,cnt[i]=1,size[i]=1,
    maxn[i]=val[sa[i]],minn[i]=val[sa[i]];
  sort(t+2,t+n+1,cmp1);
  memset(ans,128,sizeof(ans));
  for(int i=2;i<=n;i++)
  {
    int x=t[i],y=x-1,h=height[x];
    x=find(x);y=find(y);
    sum[h]+=(ll)size[x]*size[y];
    ans[h]=max(ans[h],(ll)maxn[x]*maxn[y]);
    ans[h]=max(ans[h],(ll)minn[x]*minn[y]);
    fa[y]=x;size[x]+=size[y];
    maxn[x]=max(maxn[x],maxn[y]);
    minn[x]=min(minn[x],minn[y]);
  }
  for(int i=n-1;i>=0;i--)
    sum[i]+=sum[i+1],ans[i]=max(ans[i],ans[i+1]);
}

int main()
{
  scanf("%d %s",&n,s+1);m=128;
  for(int i=1;i<=n;i++)scanf("%d",&val[i]);
  get_sa();get_height();solve();
  for(int i=0;i<n;i++)
    if(sum[i])printf("%lld %lld\n",sum[i],ans[i]);
    else printf("0 0\n");
  return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值