【BZOJ4199】品酒大会(NOI2015)-后缀数组+并查集

测试地址:品酒大会
做法:本题需要用到后缀数组+并查集。
不难想到,对题目所给的字符串构造后缀数组,那么后缀数组上两个后缀之间最小的height就是这两杯酒满足的最大相似度。我们要求的,实际上是对于每个相似值k,对于后缀数组上height大于等于k的边连接成的每个连续区间求出方案数和最大乘积,然后对方案数累加,对最大乘积取最大值。不难看出若区间中包含i个后缀,那么方案数有i(i1)/2个,而最大乘积则有可能是区间中最大和次大值的乘积或最小和次小值的乘积(因为可能有负数,负负得正)。我们可以按照height从大到小的顺序连边,然后合并区间的信息,很明显可以用并查集处理这一过程,然后这一题就做完了。
以下是本人代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll inf=1000000000;
int n,SA[300010],c[300010],x[600010]={0},s[300010],height[300010],Rank[300010];
int st[300010],top,fa[300010],order[300010];
char S[300010]={0};
ll a[300010],siz[300010]={0},cnt=0,ans=-inf*inf;
ll mx[300010],smx[300010],mn[300010],smn[300010],Cnt[300010],Ans[300010];

void calc_SA()
{
    int p=1,tot=26;
    while (p<n)
    {
        memset(c,0,sizeof(c));
        for(int i=1;i<=n;i++) c[x[i+p]]++;
        for(int i=1;i<=tot;i++) c[i]+=c[i-1];
        for(int i=tot;i>=1;i--) c[i]=c[i-1];
        c[0]=0;
        for(int i=1;i<=n;i++) s[++c[x[i+p]]]=i;

        memset(c,0,sizeof(c));
        for(int i=1;i<=n;i++) c[x[s[i]]]++;
        for(int i=1;i<=tot;i++) c[i]+=c[i-1];
        for(int i=tot;i>=1;i--) c[i]=c[i-1];
        c[0]=0;
        for(int i=1;i<=n;i++) SA[++c[x[s[i]]]]=s[i];

        tot=0;
        for(int i=1;i<=n;i++) 
        {
            if (i==1||x[SA[i]]!=x[SA[i-1]]||x[SA[i]+p]!=x[SA[i-1]+p]) tot++;
            s[SA[i]]=tot;
        }

        for(int i=1;i<=n;i++) x[i]=s[i];
        if (tot==n) break;
        p<<=1;
    }
    for(int i=1;i<=n;i++)
        Rank[SA[i]]=i;
}

void calc_height()
{
    for(int i=1,j=0;i<=n;i++,j=max(0,j-1))
    {
        if (Rank[i]==n) {height[Rank[i]]=j=0;continue;}
        while (S[i+j]==S[SA[Rank[i]+1]+j]) j++;
        height[Rank[i]]=j;
    }
}

void init(int x)
{
    fa[x]=x;siz[x]=1;
    mx[x]=-inf*inf;
    smx[x]=-inf*inf;
    mn[x]=inf*inf;
    smn[x]=inf*inf;
}

void insert(int x,ll y)
{
    if (y>=mx[x]) smx[x]=mx[x],mx[x]=y;
    else if (y>=smx[x]) smx[x]=y;
    if (y<=mn[x]) smn[x]=mn[x],mn[x]=y;
    else if (y<=smn[x]) smn[x]=y;
}

int find(int x)
{
    int r=x,i=x,j;
    while (r!=fa[r]) r=fa[r];
    while (i!=r) {j=fa[i],fa[i]=r,i=j;}
    return r;
}

void merge(int x,int y)
{
    int fx=find(x),fy=find(y);
    ll X=siz[fx],Y=siz[fy],Z=siz[fx]+siz[fy];
    fa[fy]=fx;
    siz[fx]+=siz[fy];
    insert(fx,mx[fy]);
    if (siz[fy]>1) insert(fx,smx[fy]);
    if (siz[fy]>2) insert(fx,mn[fy]);
    if (siz[fy]>3) insert(fx,smn[fy]);
    cnt=cnt-X*(X-1)/2-Y*(Y-1)/2+Z*(Z-1)/2;
    ans=max(ans,max(mx[fx]*smx[fx],mn[fx]*smn[fx]));
}

void solve()
{
    memset(c,0,sizeof(c));
    for(int i=1;i<n;i++) c[height[i]]++;
    for(int i=1;i<n;i++) c[i]+=c[i-1];
    for(int i=n;i>=1;i--) c[i]=c[i-1];
    c[0]=0;
    for(int i=1;i<n;i++)
        order[++c[height[i]]]=i;
    for(int i=1;i<=n;i++)
    {
        init(i);
        insert(i,a[SA[i]]);
    }
    int j=n-1;
    for(int i=n-1;i>=1;i--)
    {
        while (height[order[i]]<j)
        {
            Cnt[j]=cnt;
            Ans[j]=cnt?ans:0;
            j--;
        }
        merge(order[i],order[i]+1);
    }
    while (j>=0)
    {
        Cnt[j]=cnt;
        Ans[j]=cnt?ans:0;
        j--;
    }
}

int main()
{
    scanf("%d",&n);
    scanf("%s",S+1);
    for(int i=1;i<=n;i++)
        x[i]=S[i]-'a'+1;
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);

    calc_SA();
    calc_height();
    solve();

    for(int i=0;i<n;i++)
        printf("%lld %lld\n",Cnt[i],Ans[i]);

    return 0;
}
posted @ 2018-03-08 21:28  Maxwei_wzj  阅读(112)  评论(0编辑  收藏  举报