【BZOJ4199】品酒大会,后缀数组+并查集维护

Time:2016.05.23
Author:xiaoyimi
转载注明出处谢谢


传送门
题面及样例
思路:
求lcp相关问题上有类似于差异的地方,但不完全是,”差异“所求的是lcp长度总和,而这里要对每个长度的lcp统计出现次数,更加精细
以前单调栈的做法不能满足我们了,只能另辟蹊径
(一开始我想的是线段树啥的……)
考虑这样一种思路,一开始每个后缀都是一个独立的集合,对于每个height[i],它可以把两个后缀集合x,y合并,那么对ans[height[i]]的贡献是siz[x]*siz[y],可以对最大乘积的贡献是max(这两个集合最大值的乘积,最小值的乘积)
用并查集维护每个集合的左端点,右端点,最小值和最大值
之后的事情就比较容易了
注意:
对于0相似的情况,个数肯定是n*(n-1)/2,最大值肯定是max(次大值最大值,最小值次小值)
代码:

#include<bits/stdc++.h>
#define M 300004
#define LL long long
using namespace std;
int len;
char s[M];
int minn[M],maxn[M],a[M],w[M],fa[M],sa[M],rank[M],cnt[M],tmp[M],id[M],L[M],R[M];
LL ans[2][M];
struct node
{
    int data,id;
    bool operator <(const node other)const
    {
        return data>other.data;
    }
}height[M];
int in()
{
    char ch=getchar();int t=0;bool f=0; 
    while (!isdigit(ch)) f=(ch=='-'),ch=getchar();
    while (isdigit(ch)) t=(t<<1)+(t<<3)+ch-48,ch=getchar();
    return f?-t:t;
} 
void SA(int len,int up)
{
    int *rk=rank,*t=tmp,d=1,p=0;
    for (int i=0;i<len;i++) cnt[rk[i]=w[i]]++;
    for (int i=1;i<up;i++) cnt[i]+=cnt[i-1];
    for (int i=len-1;i>=0;i--) sa[--cnt[rk[i]]]=i;
    for (;;)
    {
        for (int i=len-d;i<len;i++) id[p++]=i;
        for (int i=0;i<len;i++)
            if (sa[i]>=d) id[p++]=sa[i]-d;
        for (int i=0;i<up;i++) cnt[i]=0;
        for (int i=0;i<len;i++) cnt[t[i]=rk[id[i]]]++;
        for (int i=1;i<up;i++) cnt[i]+=cnt[i-1];
        for (int i=len-1;i>=0;i--) sa[--cnt[t[i]]]=id[i];
        swap(t,rk);
        p=1;
        rk[sa[0]]=0;
        for (int i=0;i<len-1;i++)
            if (sa[i]+d<len&&sa[i+1]+d<len&&t[sa[i]]==t[sa[i+1]]&&t[sa[i]+d]==t[sa[i+1]+d])
                rk[sa[i+1]]=p-1;
            else
                rk[sa[i+1]]=p++;
        if (p==len) break;
        d<<=1;up=p;p=0;
    }
}
void Height(int len)
{
    for (int i=1;i<=len;i++) rank[sa[i]]=i;
    int k=0,x;
    for (int i=0;i<len;i++)
    {
        k=max(k-1,0);
        x=sa[rank[i]-1];
        while (w[i+k]==w[x+k]) k++;
        height[rank[i]]=(node){k,rank[i]};
    }
}
int find(int x){return fa[x]=(x!=fa[x]?find(fa[x]):fa[x]);}
main()
{
    len=in();
    scanf("%s",s);
    memset(ans[1],-127,sizeof(ans[1]));
    for (int i=0;i<len;i++) a[i]=in();
    for (int i=0;i<len;i++) w[i]=s[i]-'a'+1;
    SA(len+1,28);
    Height(len);
    sort(height+1,height+len+1);
    for (int i=1;i<=len;i++) fa[i]=i,L[i]=i,R[i]=i,minn[i]=maxn[i]=a[sa[i]];
    for (int i=1;i<=len;i++)
    if (!height[i].data) break;
    else
    {
        int p=find(height[i].id),q=find(height[i].id-1);
        ans[0][height[i].data]+=(LL)(R[p]-L[p]+1)*(R[q]-L[q]+1);
        ans[1][height[i].data]=max((LL)minn[p]*minn[q],max(ans[1][height[i].data],(LL)maxn[p]*maxn[q]));
        fa[p]=q;
        L[q]=min(L[p],L[q]);
        R[q]=max(R[p],R[q]);
        minn[q]=min(minn[p],minn[q]); 
        maxn[q]=max(maxn[p],maxn[q]);
    }
    for (int i=len-1;i>=1;i--)
        ans[0][i]+=ans[0][i+1],
        ans[1][i]=max(ans[1][i],ans[1][i+1]);
    sort(a,a+len);
    printf("%lld %lld\n",(LL)len*(len-1)>>1,max((LL)a[1]*a[0],(LL)a[len-2]*a[len-1]));
    for (int i=1;i<len;i++)
    printf("%lld %lld\n",ans[0][i],!ans[0][i]?0:ans[1][i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值