[BZOJ4199][UOJ#131]【NOI2015】品酒大会

Description

给出一个长度为 n 的字符串,每一位有一个权值 val。定义两个位字符为 r 相似,是指分别从这两个字符开始,到后面的 r 个字符都相等。两个 r 相似的字符还有一个权值为这两个字符权值的乘积。问对于 r = 0, 1, 2, … , n - 1,统计出有多少种方法可以选出 2 个“r 相似”的字符,并回答选择 2 个”r 相似”的字符可以得到的权值的最大值。
n<=3*10^5
|val| <=10^9

Solution

i和j这两个位置的字符r相似其实就是它们开头的后缀的最长公共前缀大于等于r

那么可以先将height构造出来

对于两个位置i和j,它们排名之间的height的最小值要大于等于r

按照height排序,从大到小做,每扫到一个height那么就利用并查集将它和它的前一名缩起来,并更新相应的r的答案,一开始这个答案还要从之前的继承过来

计算方案数,那么只需要维护并查集时集合的size,合并就相乘计入答案
计算最大值,因为有可能有负数,那么维护集合的最大值和最小值,看乘起来哪个更大

Code

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define imax(a,b) ((a>b)?a:b)
#define N 300005
#define LL long long
using namespace std;
char st[N];
int s2[N],SA[N],rank[N],r1[N],ct[N],n,height[N],d[N],f[N];
LL a[N],ans[N],cnt[N],sz[N],mx[N],mi[N];
void prp()
{
    int m=0;
    memset(ct,0,sizeof(ct));
    fo(i,1,n) ct[rank[i]=st[i]-'a'+1]++,m=imax(m,rank[i]);
    fo(i,1,m) ct[i]+=ct[i-1];
    fod(i,n,1) SA[ct[rank[i]]--]=i;
    for(int j=1,k=0;k<n;j<<=1,m=k)
    {
        memset(ct,0,sizeof(ct));
        int p=0;
        fo(i,n-j+1,n) s2[++p]=i;
        fo(i,1,n) if(SA[i]>j) s2[++p]=SA[i]-j;
        fo(i,1,n) ct[rank[s2[i]]]++;
        fo(i,1,m) ct[i]+=ct[i-1];
        fod(i,n,1) SA[ct[rank[s2[i]]]--]=s2[i];
        r1[SA[1]]=k=1;
        fo(i,2,n) r1[SA[i]]=(rank[SA[i]]==rank[SA[i-1]]&&rank[SA[i]+j]==rank[SA[i-1]+j])?k:++k;
        fo(i,1,n) rank[i]=r1[i]; 
    } 
}

void geth()
{
    height[1]=0;
    fo(i,1,n)
    {
        if(rank[i]==1) continue;
        int p=imax(0,height[rank[i-1]]-1);
        while(st[i+p]==st[SA[rank[i]-1]+p]) p++; 
        height[rank[i]]=p;
    }
}
bool cmp(int x,int y)
{
    return height[x]>height[y];
}
int getf(int k)
{
    if(f[k]==0||f[k]==k) return k;
    return f[k]=getf(f[k]);
}
int main()
{
    cin>>n;
    scanf("\n%s",st+1);
    fo(i,1,n) scanf("%lld",&a[i]);
    prp();
    geth();
    fo(i,1,n) d[i]=i,sz[i]=1,mx[i]=mi[i]=a[SA[i]];
    sort(d+1,d+1+n,cmp);
    fo(i,0,n) ans[i]=-7000000000000000000;
    fo(i,1,n)
    {
        int w=height[d[i]];
        if(w!=height[d[i-1]]) cnt[w]+=cnt[height[d[i-1]]],ans[w]=max(ans[w],ans[height[d[i-1]]]); 
        if(d[i]==1) continue;
        int fx=getf(d[i]),fy=getf(d[i]-1);
        cnt[w]+=sz[fx]*sz[fy];
        ans[w]=max(ans[w],max(mx[fx]*mx[fy],mi[fx]*mi[fy]));
        f[fx]=fy,sz[fy]+=sz[fx],mx[fy]=max(mx[fy],mx[fx]),mi[fy]=min(mi[fy],mi[fx]);
    }
    fod(i,n-1,0) 
    {
        ans[i]=max(ans[i],ans[i+1]);
        if(cnt[i]==0) cnt[i]=cnt[i+1];
    }
    fo(i,0,n-1) 
    {
        if(ans[i]==-7000000000000000000) ans[i]=0;
        printf("%lld %lld\n",cnt[i],ans[i]); 
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值