[BZOJ4199][Noi2015]品酒大会

25 篇文章 0 订阅

原题地址

首先是正解部分…

我们先用后缀数组将后缀排序并得到hei数组…
然后正解的做法是用合并后缀集合的方法维护信息(太神了自己想根本想不到OLZ…
初始每个后缀自己构成一个大小为1的集合。按hei[i]从大到小枚举每个后缀,将rank为i的后缀所在集合与rank为i-1的后缀所在集合合并(此时对于前一集合中任意后缀T1与后一集合中任意后缀T2,都有LCP(T1,T2)=hei[i]),具体怎么合并和维护信息看代码.

AC code:

#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
const int N=300010;
const ll  INF=1<<30;
int  n;
int  a[N],b[N],f[N],sa[N],ta[N],c[N],cc[N],rk[N],tr[N],hei[N],mx[N],mn[N],sz[N];
ll   A1[N],A2[N];
char s[N];

struct Data{
    int val,num;

    Data() {}
    Data(int val,int num):val(val),num(num) {}

    friend bool operator<(Data x,Data y){
        if(x.val!=y.val) return x.val>y.val;
        return x.num>y.num;
    }
};
vector<Data> v;

int cmp(int i,int j,int l){
    return i+l>=n||j+l>=n||rk[i]!=rk[j]||rk[i+l]!=rk[j+l];
}

void build(){
    for(int i=0;i<n;i++) c[s[i]]=1,cc[s[i]]++;
    for(int i=1;i<(1<<10);i++) c[i]+=c[i-1],cc[i]+=cc[i-1];
    for(int i=0;i<n;i++) rk[i]=c[s[i]];
    for(int i=0;i<n;i++) sa[cc[s[i]]--]=i;
    for(int i=1,j,k;rk[sa[n]]!=n;i<<=1){
        for(j=1;j<=i;j++) ta[j]=n-j;
        for(k=1;k<=n;k++) if(sa[k]>=i) ta[j++]=sa[k]-i;
        for(j=0;j<n;j++) c[j]=0;
        for(j=0;j<n;j++) c[rk[j]]++;
        for(j=1;j<n;j++) c[j]+=c[j-1];
        for(j=n;j>0;j--) sa[c[rk[ta[j]]]--]=ta[j];
        for(tr[sa[1]]=1,j=2;j<=n;j++) tr[sa[j]]=tr[sa[j-1]]+cmp(sa[j],sa[j-1],i);
        for(j=0;j<n;j++) rk[j]=tr[j];
    }
    for(int i=0,j=0;i<n;hei[rk[i]]=j,j=j?j-1:0,i++){
        for(;rk[i]!=1&&s[sa[rk[i]]+j]==s[sa[rk[i]-1]+j];j++) ;
    }
}

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

void work(){
    for(int i=1;i<=n;i++) f[i]=i;
    for(int i=1;i<=n;i++) sz[i]=1;
    for(int i=1;i<=n;i++) b[rk[i-1]]=a[i];
    for(int i=1;i<=n;i++) mx[i]=mn[i]=b[i];
    for(int i=0;i<n;i++) A2[i]=-(INF*INF);
    for(int i=1;i<=n;i++) v.push_back(Data(hei[i],i));
    sort(v.begin(),v.end());
    for(int i=0;i<n-1;i++){
        int x=find(v[i].num-1),y=find(v[i].num);
        ll  a=mx[x],b=mn[x],c=mx[y],d=mn[y];
        A1[v[i].val]+=(ll)sz[x]*(ll)sz[y];
        A2[v[i].val]=max(A2[v[i].val],max(a*c,max(b*d,max(a*d,b*c))));
        f[x]=y;
        sz[y]+=sz[x];
        mx[y]=max(mx[y],mx[x]);mn[y]=min(mn[y],mn[x]);
    }
}

int main(){
    scanf("%d%s",&n,s);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    build();
    work();
    for(int i=n-2;i>=0;i--){
        A1[i]+=A1[i+1];
        A2[i]=max(A2[i],A2[i+1]);
    }
    for(int i=0;i<n;i++) printf("%lld %lld\n",A1[i],A1[i]?A2[i]:0);

    return 0;
}

接下来是吐槽部分…

这题用BZOJ3238的方法会

T !!!!!!!!!!!!!!!!!!!!

各种优化常数之后还是过不了(最大原因是倍增RMQ慢了,正解并没有用到倍增RMQ)!!!!!!!!!!!!!!!!!!!!!!最后还是T了两个点,真是跪了…

不过在优化的过程中还是有一些收获的,写一下…

  1. 区间最值那里分类讨论写复杂了(再次证明了我有多弱),之后一定要想清楚再写代码.
  2. 注意用long long会慢一倍左右,所以代码中全部变量都用long long是作死的行为…
  3. 原先的后缀数组模板还有可以改进的地方,可以少写几个for,具体看代码.

贴一下90分代码吧…

TLE code:

#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=300010;
const int LOGN=25;
const ll  INF=1<<30;
int  n;
int  a[N],b[N],lg2[N],sa[N],ta[N],c[N],cc[N],rk[N],tr[N],hei[N];
int  f[N][LOGN],g1[N][LOGN],g2[N][LOGN];
ll   A1[N],A2[N];
char s[N];

int cmp(int i,int j,int l){
    return i+l>=n||j+l>=n||rk[i]!=rk[j]||rk[i+l]!=rk[j+l];
}

void build(){
    for(int i=0;i<n;i++) c[s[i]]=1,cc[s[i]]++;
    for(int i=1;i<(1<<10);i++) c[i]+=c[i-1],cc[i]+=cc[i-1];
    for(int i=0;i<n;i++) rk[i]=c[s[i]];
    for(int i=0;i<n;i++) sa[cc[s[i]]--]=i;
    for(int i=1,j,k;rk[sa[n]]!=n;i<<=1){
        for(j=1;j<=i;j++) ta[j]=n-j;
        for(k=1;k<=n;k++) if(sa[k]>=i) ta[j++]=sa[k]-i;
        for(j=0;j<n;j++) c[j]=0;
        for(j=0;j<n;j++) c[rk[j]]++;
        for(j=1;j<n;j++) c[j]+=c[j-1];
        for(j=n;j>0;j--) sa[c[rk[ta[j]]]--]=ta[j];
        for(tr[sa[1]]=1,j=2;j<=n;j++) tr[sa[j]]=tr[sa[j-1]]+cmp(sa[j],sa[j-1],i);
        for(j=0;j<n;j++) rk[j]=tr[j];
    }
    for(int i=0,j=0;i<n;hei[rk[i]]=j,j=j?j-1:0,i++){
        for(;rk[i]!=1&&s[sa[rk[i]]+j]==s[sa[rk[i]-1]+j];j++) ;
    }
    for(int i=1;i<=n;i++) b[i]=a[sa[i]+1];
    for(int i=1;i<=n;i++) a[i]=b[i];
    for(int i=1;i<=n;i++){
        f[i][0]=hei[i];
        g1[i][0]=g2[i][0]=a[i];
    }
    for(int i=1,j;i<=lg2[n];i++){
        for(j=1;j<=n;j++){
            int tmp=j+(1<<(i-1))>n?0:(1<<(i-1));
            f[j][i]=f[j][i-1]<f[j+tmp][i-1]?f[j][i-1]:f[j+tmp][i-1];
            g1[j][i]=g1[j][i-1]>g1[j+tmp][i-1]?g1[j][i-1]:g1[j+tmp][i-1];
            g2[j][i]=g2[j][i-1]<g2[j+tmp][i-1]?g2[j][i-1]:g2[j+tmp][i-1];
        }
    }
}

int Max(int g[N][LOGN],int L,int R){
    return max(g[L][lg2[R-L+1]],g[R-(1<<(lg2[R-L+1]))+1][lg2[R-L+1]]);
}

int Min(int g[N][LOGN],int L,int R){
    return min(g[L][lg2[R-L+1]],g[R-(1<<(lg2[R-L+1]))+1][lg2[R-L+1]]);
}

int main(){
    scanf("%d%s",&n,s);
    for(int i=1;i<=n;i++) lg2[i]=(int)log2(i);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    build();
    for(int i=0;i<n;i++) A2[i]=-(INF*INF);
    for(int i=2;i<=n;i++){
        int L1=1,R1=i,L2=i,R2=n+1;
        while(L1+1!=R1){
            int M=(L1+R1)>>1;
            if(Min(f,M,i-1)>hei[i]) R1=M;
            else L1=M;
        }
        while(L2+1!=R2){
            int M=(L2+R2)>>1;
            if(Min(f,i,M)<hei[i]) R2=M;
            else L2=M;
        }
        A1[hei[i]]+=(ll)(i-R1+1)*(ll)(L2-i+1);
        ll a=Max(g1,R1-1,i-1),b=Max(g1,i,L2),c=Min(g2,R1-1,i-1),d=Min(g2,i,L2);
        A2[hei[i]]=max(A2[hei[i]],max(a*b,max(c*d,max(a*d,b*c))));
    }
    for(int i=n-2;i>=0;i--){
        A1[i]+=A1[i+1];
        A2[i]=max(A2[i],A2[i+1]);
    }
    for(int i=0;i<n;i++) printf("%lld %lld\n",A1[i],A1[i]?A2[i]:0);

    return 0; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值