1. 程式人生 > >【BZOJ4199】【NOI2015】品酒大會(後綴數組)

【BZOJ4199】【NOI2015】品酒大會(後綴數組)

可能 urn update cnblogs merge pri 需要 can 更新

【BZOJ4199】【NOI2015】品酒大會

題面

BZOJ
Uoj
洛谷

題解

考慮最裸的暴力
枚舉每次的長度
以及兩個開始的位置
檢查以下是否滿足條件,如果可以直接更新答案
復雜度\(O(n^3)\)
\(15~20\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue> using namespace std; #define ll long long #define MAX 320000 inline int read() { int x=0,t=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')t=-1,ch=getchar(); while(ch<='9'&&ch>='0'
)x=x*10+ch-48,ch=getchar(); return x*t; } int n; char s[MAX]; int lg[MAX],v[MAX]; struct SA { int p[20][MAX],a[MAX]; int x[MAX],y[MAX],t[MAX]; int SA[MAX],height[MAX],rk[MAX]; bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];} void init() { memset(SA,0
,sizeof(SA)); memset(height,0,sizeof(height)); memset(rk,0,sizeof(rk)); memset(x,0,sizeof(x)); memset(y,0,sizeof(y)); memset(t,0,sizeof(t)); memset(a,0,sizeof(a)); } void GetSA() { int m=50; for(int i=1;i<=n;++i)t[x[i]=a[i]]++; for(int i=1;i<=m;++i)t[i]+=t[i-1]; for(int i=n;i>=1;--i)SA[t[x[i]]--]=i; for(int k=1;k<=n;k<<=1) { int p=0; for(int i=n-k+1;i<=n;++i)y[++p]=i; for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k; for(int i=0;i<=m;++i)t[i]=0; for(int i=1;i<=n;++i)t[x[y[i]]]++; for(int i=1;i<=m;++i)t[i]+=t[i-1]; for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i]; swap(x,y); x[SA[1]]=p=1; for(int i=2;i<=n;++i) x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p; if(p>=n)break; m=p; } for(int i=1;i<=n;++i)rk[SA[i]]=i; for(int i=1,j=0;i<=n;++i) { if(j)--j; while(a[i+j]==a[SA[rk[i]-1]+j])++j; height[rk[i]]=j; } } void Pre() { memset(p,63,sizeof(p)); for(int i=1;i<=n;++i)p[0][i]=height[i]; for(int j=1;j<20;++j) for(int i=1;i<=n;++i) p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]); } int Query(int i,int j) { return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]); } int lcp(int i,int j) { int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]); return Query(l,r); } }SA; int main() { n=read(); for(int i=2;i<=n;++i)lg[i]=lg[i>>1]+1; scanf("%s",s+1); for(int i=1;i<=n;++i)SA.a[i]=s[i]-96; SA.GetSA();SA.Pre(); for(int i=1;i<=n;++i)v[i]=read(); for(int len=0;len<n;++len) { ll ans1=0,ans2=-1e18; for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) { if(SA.lcp(i,j)>=len) ans1++,ans2=max(ans2,1ll*v[i]*v[j]); } if(ans1)printf("%lld %lld\n",ans1,ans2); else { for(int j=len;j<n;++j)puts("0 0"); break; } } return 0; }

繼續考慮,
觀察到如果兩杯酒是\(k\)相似的
那麽,他們一定是\(j(j<=k)\)相似的
隨意只需要枚舉兩杯酒
檢查他們是多少相似
然後做一個前綴和就好了
復雜度\(O(n^2)\)
\(40\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 320000
inline int read()
{
    int x=0,t=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
int n;
char s[MAX];
int lg[MAX],v[MAX];
ll ans1[MAX],ans2[MAX];
struct SA
{
    int p[20][MAX],a[MAX];
    int x[MAX],y[MAX],t[MAX];
    int SA[MAX],height[MAX],rk[MAX];
    bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
    void init()
        {
            memset(SA,0,sizeof(SA));
            memset(height,0,sizeof(height));
            memset(rk,0,sizeof(rk));
            memset(x,0,sizeof(x));
            memset(y,0,sizeof(y));
            memset(t,0,sizeof(t));
            memset(a,0,sizeof(a));
        }
    void GetSA()
        {
            int m=50;
            for(int i=1;i<=n;++i)t[x[i]=a[i]]++;
            for(int i=1;i<=m;++i)t[i]+=t[i-1];
            for(int i=n;i>=1;--i)SA[t[x[i]]--]=i;
            for(int k=1;k<=n;k<<=1)
            {
                int p=0;
                for(int i=n-k+1;i<=n;++i)y[++p]=i;
                for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
                for(int i=0;i<=m;++i)t[i]=0;
                for(int i=1;i<=n;++i)t[x[y[i]]]++;
                for(int i=1;i<=m;++i)t[i]+=t[i-1];
                for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i];
                swap(x,y);
                x[SA[1]]=p=1;
                for(int i=2;i<=n;++i)
                    x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
                if(p>=n)break;
                m=p;
            }
            for(int i=1;i<=n;++i)rk[SA[i]]=i;
            for(int i=1,j=0;i<=n;++i)
            {
                if(j)--j;
                while(a[i+j]==a[SA[rk[i]-1]+j])++j;
                height[rk[i]]=j;
            }
        }
    void Pre()
        {
            memset(p,63,sizeof(p));
            for(int i=1;i<=n;++i)p[0][i]=height[i];
            for(int j=1;j<20;++j)
                for(int i=1;i<=n;++i)
                    p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]);
        }
    int Query(int i,int j)
        {
            return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]);
        }
    int lcp(int i,int j)
        {
            int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]);
            return Query(l,r);
        }
}SA;
int main()
{
    n=read();
    for(int i=2;i<=n;++i)lg[i]=lg[i>>1]+1;
    scanf("%s",s+1);
    for(int i=1;i<=n;++i)SA.a[i]=s[i]-96;
    SA.GetSA();SA.Pre();
    for(int i=1;i<=n;++i)v[i]=read();
    memset(ans2,-63,sizeof(ans2));
    for(int i=1;i<=n;++i)
        for(int j=i+1;j<=n;++j)
        {
            int len=SA.lcp(i,j);
            ans1[0]++;ans1[len+1]--;
            ans2[len]=max(ans2[len],1ll*v[i]*v[j]);
        }
    for(int i=1;i<=n;++i)ans1[i]+=ans1[i-1];
    for(int i=n;i>=0;--i)ans2[i]=max(ans2[i],ans2[i+1]);
    for(int i=0;i<n;++i)printf("%lld %lld\n",ans1[i],!ans1[i]?0:ans2[i]);
    return 0;
}

如果我們求出\(height\)數組之後
枚舉一個長度\(len\),按照\(height\)分類
如果一段連續的\(height\)都不小於了\(len\)
證明這一段都會產生貢獻
所以記錄這一段產生的貢獻,
至於最大值,就在這一段裏面記錄最大,次大,最小,次小值
拼起來算一下

因為\(height\)最大值可以很小
所以這樣可以過\(50\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 320000
inline int read()
{
    int x=0,t=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
int n;
char s[MAX];
int lg[MAX],v[MAX];
ll ans1[MAX],ans2[MAX];
struct SA
{
    int p[20][MAX],a[MAX];
    int x[MAX],y[MAX],t[MAX];
    int SA[MAX],height[MAX],rk[MAX];
    bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
    void init()
        {
            memset(SA,0,sizeof(SA));
            memset(height,0,sizeof(height));
            memset(rk,0,sizeof(rk));
            memset(x,0,sizeof(x));
            memset(y,0,sizeof(y));
            memset(t,0,sizeof(t));
            memset(a,0,sizeof(a));
        }
    void GetSA()
        {
            int m=50;
            for(int i=1;i<=n;++i)t[x[i]=a[i]]++;
            for(int i=1;i<=m;++i)t[i]+=t[i-1];
            for(int i=n;i>=1;--i)SA[t[x[i]]--]=i;
            for(int k=1;k<=n;k<<=1)
            {
                int p=0;
                for(int i=n-k+1;i<=n;++i)y[++p]=i;
                for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
                for(int i=0;i<=m;++i)t[i]=0;
                for(int i=1;i<=n;++i)t[x[y[i]]]++;
                for(int i=1;i<=m;++i)t[i]+=t[i-1];
                for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i];
                swap(x,y);
                x[SA[1]]=p=1;
                for(int i=2;i<=n;++i)
                    x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
                if(p>=n)break;
                m=p;
            }
            for(int i=1;i<=n;++i)rk[SA[i]]=i;
            for(int i=1,j=0;i<=n;++i)
            {
                if(j)--j;
                while(a[i+j]==a[SA[rk[i]-1]+j])++j;
                height[rk[i]]=j;
            }
        }
    void Pre()
        {
            memset(p,63,sizeof(p));
            for(int i=1;i<=n;++i)p[0][i]=height[i];
            for(int j=1;j<20;++j)
                for(int i=1;i<=n;++i)
                    p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]);
        }
    int Query(int i,int j)
        {
            return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]);
        }
    int lcp(int i,int j)
        {
            int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]);
            return Query(l,r);
        }
}SA;
bool cmp(int a,int b){return SA.height[a]>SA.height[b];}
int id[MAX];
void update(int x,int &zd,int &cd,int &zx,int &cx)
{
    if(x>zd)cd=zd,zd=x;
    else if(x>cd)cd=x;
    if(x<zx)cx=zx,zx=x;
    else if(x<cx)cx=x;
}
ll check_max(int zd,int cd,int zx,int cx)
{
    ll ret=-1e18;
    if(zd!=-2e9&&cd!=-2e9)ret=max(ret,1ll*zd*cd);
    if(zx!=+2e9&&cx!=+2e9)ret=max(ret,1ll*zx*cx);
    return ret;
}
int main()
{
    n=read();
    for(int i=2;i<=n;++i)lg[i]=lg[i>>1]+1;
    scanf("%s",s+1);
    for(int i=1;i<=n;++i)SA.a[i]=s[i]-96;
    SA.GetSA();SA.Pre();
    for(int i=1;i<=n;++i)v[i]=read(),id[i]=i;;
    memset(ans2,-63,sizeof(ans2));

    sort(&id[1],&id[n+1],cmp);

    for(int len=0;len<=n;++len)
    {
        int zd,zx,cd,cx,cnt=0;
        zd=cd=-2e9;zx=cx=2e9;
        for(int i=2;i<=n;++i)
        {
            if(SA.height[i]<len)
            {
                ans2[len]=max(ans2[len],check_max(zd,cd,zx,cx));
                zd=cd=-2e9;zx=cx=2e9;
                cnt=0;
            }
            else
            {
                update(v[SA.SA[i]],zd,cd,zx,cx);
                if(!cnt)update(v[SA.SA[i-1]],zd,cd,zx,cx);
                ans1[len]+=cnt;
                if(!cnt)ans1[len]++,cnt++;
                cnt++;
            }
        }
        ans2[len]=max(ans2[len],check_max(zd,cd,zx,cx));
        if(!ans1[len])break;
    }
    for(int i=n;i>=0;--i)ans2[i]=max(ans2[i],ans2[i+1]);
    for(int i=0;i<n;++i)printf("%lld %lld\n",ans1[i],!ans1[i]?0:ans2[i]);
    return 0;
}

想想上面的東西怎麽優化?
我們每次從小往大枚舉
如果有一段連續的\(height\)都大於了\(len\)
那麽,我們在\(0..len-1\)的時候也都會被枚舉一遍

所以,我們考慮從大到小枚舉
如果有一段連續的區間,那我們可以直接把他們縮成一個區間
同時記錄這個區間的大小,以及最大,最小值

這樣的話,每次的枚舉可以把一段區間變成一個點

考慮這個思路,也不可能每次掃一邊所有的值

所以直接把\(height\)從大到小排序
每次處理一個\(height\)就合並兩個集合
並且計算產生的貢獻
最後求一個後綴和就好

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 320000
inline int read()
{
    int x=0,t=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
int n,v[MAX];
char s[MAX];
ll ans1[MAX],ans2[MAX];
struct SA
{
    int a[MAX];
    int x[MAX],y[MAX],t[MAX];
    int SA[MAX],height[MAX],rk[MAX];
    bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
    void GetSA()
        {
            int m=50;
            for(int i=1;i<=n;++i)t[x[i]=a[i]]++;
            for(int i=1;i<=m;++i)t[i]+=t[i-1];
            for(int i=n;i>=1;--i)SA[t[x[i]]--]=i;
            for(int k=1;k<=n;k<<=1)
            {
                int p=0;
                for(int i=n-k+1;i<=n;++i)y[++p]=i;
                for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
                for(int i=0;i<=m;++i)t[i]=0;
                for(int i=1;i<=n;++i)t[x[y[i]]]++;
                for(int i=1;i<=m;++i)t[i]+=t[i-1];
                for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i];
                swap(x,y);
                x[SA[1]]=p=1;
                for(int i=2;i<=n;++i)
                    x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
                if(p>=n)break;
                m=p;
            }
            for(int i=1;i<=n;++i)rk[SA[i]]=i;
            for(int i=1,j=0;i<=n;++i)
            {
                if(j)--j;
                while(a[i+j]==a[SA[rk[i]-1]+j])++j;
                height[rk[i]]=j;
            }
        }
}SA;
bool cmp(int a,int b){return SA.height[a]>SA.height[b];}
int id[MAX];
int f[MAX],mm[MAX],mi[MAX],size[MAX];
ll ans[MAX];
int getf(int x){return x==f[x]?x:f[x]=getf(f[x]);}
void Merge(int x,int y,int len)
{
    x=getf(x);y=getf(y);
    f[y]=x;
    ans1[len]+=1ll*size[x]*size[y];
    size[x]+=size[y];
    ans[x]=max(ans[x],ans[y]);
    ans[x]=max(ans[x],max(1ll*mm[x]*mm[y],1ll*mi[x]*mi[y]));
    ans[x]=max(ans[x],max(1ll*mm[x]*mi[y],1ll*mi[x]*mm[y]));
    mm[x]=max(mm[x],mm[y]);
    mi[x]=min(mi[x],mi[y]);
    ans2[len]=max(ans2[len],ans[x]);
}
int main()
{
    n=read();
    scanf("%s",s+1);
    for(int i=1;i<=n;++i)SA.a[i]=s[i]-96;
    SA.GetSA();
    for(int i=1;i<=n;++i)v[i]=read(),id[i]=i;;
    for(int i=1;i<=n;++i)f[i]=i,size[i]=1,mm[i]=mi[i]=v[i],ans[i]=-1e18;
    memset(ans2,-63,sizeof(ans2));
    sort(&id[2],&id[n+1],cmp);
    for(int i=2;i<=n;++i)
        Merge(SA.SA[id[i]],SA.SA[id[i]-1],SA.height[id[i]]);
    for(int i=n;i>=0;--i)ans1[i]+=ans1[i+1];
    for(int i=n;i>=0;--i)ans2[i]=max(ans2[i],ans2[i+1]);
    for(int i=0;i<n;++i)printf("%lld %lld\n",ans1[i],!ans1[i]?0:ans2[i]);
    return 0;
}

【BZOJ4199】【NOI2015】品酒大會(後綴數組)