bzoj 4199: [Noi2015]品酒大会 后缀数组+并查集

题意

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

分析

先构造出height数组,然后把height数组从大到小排序,并逐个加入。每加入一个就用并查集维护一下数量,最大值次大值,最小值次小值并计算一波贡献即可。

一开始用vector来排序height数组结果T了,改成排序才过。。。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;

const int N=300005;
const int inf=0x7fffffff;

int n,b[N],c[N],d[N],rank[N*2],sa[N],height[N],f[N],size[N],mx1[N],mx2[N],mn1[N],mn2[N],a[N],num[N];
LL ans[N],mx[N];
char s[N];

void get_sa(int n,int m)
{
    for (int i=1;i<=n;i++) b[s[i]]++;
    for (int i=1;i<=m;i++) b[i]+=b[i-1];
    for (int i=n;i>=1;i--) c[b[s[i]]--]=i;
    int t=0;
    for (int i=1;i<=n;i++)
    {
        if (s[c[i]]!=s[c[i-1]]) t++;
        rank[c[i]]=t;
    }
    int j=1;
    while (j<=n)
    {
        for (int i=1;i<=n;i++) b[i]=0;
        for (int i=1;i<=n;i++) b[rank[i+j]]++;
        for (int i=1;i<=n;i++) b[i]+=b[i-1];
        for (int i=n;i>=1;i--) c[b[rank[i+j]]--]=i;
        for (int i=1;i<=n;i++) b[i]=0;
        for (int i=1;i<=n;i++) b[rank[i]]++;
        for (int i=1;i<=n;i++) b[i]+=b[i-1];
        for (int i=n;i>=1;i--) d[b[rank[c[i]]]--]=c[i];
        t=0;
        for (int i=1;i<=n;i++)
        {
            if (rank[d[i]]!=rank[d[i-1]]||rank[d[i]]==rank[d[i-1]]&&rank[d[i]+j]!=rank[d[i-1]+j]) t++;
            c[d[i]]=t;
        }
        for (int i=1;i<=n;i++) rank[i]=c[i];
        if (t==n) break;
        j*=2;
    }
    for (int i=1;i<=n;i++) sa[rank[i]]=i;
}

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

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

LL calc(int x)
{
    return (LL)size[x]*(size[x]-1)/2;
}

void merge(int x,int y)
{
    size[y]+=size[x];f[x]=y;
    if (mx1[x]!=-inf)
    {
        int w=mx1[x];
        if (w>mx1[y]) mx2[y]=mx1[y],mx1[y]=w;
        else if (w>mx2[y]) mx2[y]=w;
    }
    if (mx2[x]!=-inf)
    {
        int w=mx2[x];
        if (w>mx1[y]) mx2[y]=mx1[y],mx1[y]=w;
        else if (w>mx2[y]) mx2[y]=w;
    }
    if (mn1[x]!=inf)
    {
        int w=mn1[x];
        if (w<mn1[y]) mn2[y]=mn1[y],mn1[y]=w;
        else if (w<mn2[y]) mn2[y]=w;
    }
    if (mn2[x]!=inf)
    {
        int w=mn2[x];
        if (w<mn1[y]) mn2[y]=mn1[y],mn1[y]=w;
        else if (w<mn2[y]) mn2[y]=w;
    }
}

bool cmp(int a,int b)
{
    return height[a]>height[b];
}

int main()
{
    scanf("%d",&n);
    scanf("%s",s+1);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    get_sa(n,200);
    get_height(n);
    for (int i=1;i<n;i++) num[i]=i+1;
    sort(num+1,num+n,cmp);
    for (int i=1;i<=n;i++) f[i]=i,mn1[i]=mx1[i]=a[sa[i]],mn2[i]=inf,mx2[i]=-inf,size[i]=1;
    for (int i=0;i<=n;i++) mx[i]=-(LL)inf*inf;
    int p=1;
    for (int i=n-1;i>=0;i--)
    {
        ans[i]=ans[i+1];mx[i]=mx[i+1];
        while (height[num[p]]==i&&p<n)
        {
            int x=num[p];
            ans[i]-=calc(find(x))+calc(find(x-1));
            merge(find(x),find(x-1));
            int w=find(x);
            ans[i]+=calc(w);
            mx[i]=max(mx[i],max((LL)mx1[w]*mx2[w],(LL)mn1[w]*mn2[w]));
            p++;
        }
    }
    for (int i=0;i<=n-1;i++) printf("%lld %lld\n",ans[i],mx[i]==-(LL)inf*inf?0:mx[i]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值