• [BZOJ]4199 品酒大会(Noi2015)


      讲道理是后缀数组裸题吧,虽然知道后缀数组的原理但是小C不会写是什么鬼。。

      小C趁着做这题的当儿,学习了一下后缀数组。

      网络上的后缀数组模板完全看不懂怎么破,全程照着黄学长的代码抄,感觉黄学长写得还是很优雅的。

      求LCP的部分已经崩坏了,小C自己脑补的做法是。。倍增??

      看到正确的写法之后小C内心是绝望的,大致意思是:在原字符串中,设相邻两个后缀为Sx、Sx+1,那么有height[x+1]>=height[x]-1。(height数组就是小C代码里的tp数组)

    Description

      一年一度的“幻影阁夏日品酒大会”隆重开幕了。大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个奖项,吸引了众多品酒师参加。在大会的晚餐上,调酒师 Rainbow 调制了 n 杯鸡尾酒。这 n 杯鸡尾酒排成一行,其中第 n 杯酒 (1 ≤ i ≤ n) 被贴上了一个标签si,每个标签都是 26 个小写 英文字母之一。设 str(l, r)表示第 l 杯酒到第 r 杯酒的 r − l + 1 个标签顺次连接构成的字符串。若 str(p, po) = str(q, qo),其中 1 ≤ p ≤ po ≤ n, 1 ≤ q ≤ qo ≤ n, p ≠ q, po − p + 1 = qo − q + 1 = r ,则称第 p 杯酒与第 q 杯酒是“ r 相似” 的。当然两杯“ r 相似”(r > 1)的酒同时也是“ 1 相似”、“ 2 相似”、……、“ (r − 1) 相似”的。特别地,对于任意的 1 ≤ p , q ≤ n , p ≠ q ,第 p 杯酒和第 q 杯酒都 是“ 0 相似”的。

      在品尝环节上,品酒师 Freda 轻松地评定了每一杯酒的美味度,凭借其专业的水准和经验成功夺取了“首席品酒家”的称号,其中第 i 杯酒 (1 ≤ i ≤ n) 的 美味度为 ai 。现在 Rainbow 公布了挑战环节的问题:本次大会调制的鸡尾酒有一个特点,如果把第 p 杯酒与第 q 杯酒调兑在一起,将得到一杯美味度为 ap*aq 的 酒。现在请各位品酒师分别对于 r = 0,1,2, ⋯ , n − 1 ,统计出有多少种方法可以 选出 2 杯“ r 相似”的酒,并回答选择 2 杯“ r 相似”的酒调兑可以得到的美味度的最大值。

    Input

      第 1 行包含 1 个正整数 n ,表示鸡尾酒的杯数。

      第 2 行包含一个长度为 n 的字符串 S,其中第 i 个字符表示第 i 杯酒的标签。

      第 3 行包含 n 个整数,相邻整数之间用单个空格隔开,其中第 i 个整数表示第 i 杯酒的美味度 ai 。

     

    Output

       包括 n 行。第 i 行输出 2 个整数,中间用单个空格隔开。第 1 个整 数表示选出两杯“ (i − 1) 相似”的酒的方案数,第 2 个整数表示选出两杯 “ (i − 1) 相似”的酒调兑可以得到的最大美味度。若不存在两杯“ (i − 1) 相似” 的酒,这两个数均为 0 。
     

    Sample Input

      12
      abaabaabaaba
      1 -2 3 -4 5 -6 7 -8 9 -10 11 -12

     

    Sample Output

      66 120
      34 120
      15 55
      12 40
      9 27
      7 16
      5 7
      3 -4
      2 -4
      1 -4
      0 0
      0 0

    HINT

      

    Solution

      这种字符串子串比较的题目,应该很容易都能想到后缀数组。

      我们发现如果两杯酒是k相似的,那么k一定小等于 以这两杯酒为开头的后缀 的最长公共前缀。

      我们将原串转化为排序好的后缀数组(原串之后就没用了)显然在后缀数组上相邻的最长公共前缀就是它们之间的height。

      进一步说,在后缀数组上任意两个不相等的后缀s[l]、s[r],那么它们的最长公共前缀就是

      这样我们得到了n-1个height,设height[i]=height(s[i-1],s[i])。

      height[i]对答案的贡献是存在最长的区间[l,r],使得,如果height有相同的情况,强制给他们定义大小级别。

      小C举个例子:

        

      上图中,height[7]影响答案的范围是[6,10],这样左框里的元素3、7,都可以分别和右框里的1、2、4、9配对,每个配对之间的最长公共前缀一定是1。

      (顺带一提,a数组就是题目中所述a数组,小C在此提醒方便读者阅读。)

      因此,height[7]对于为1相似的配对的数目ans1[1]的贡献是2*4=8,对于为1相似的配对的最大美味度ans2[1]的贡献是7*9=63。

      如果像上图中height[7]影响了[6,10],那么同样值等于1的height[9]影响的范围最多只能是[8,10]了,因为我们强制给它们定义了大小级别。

      统计最大值时要注意负负得正的情况,所以要同时记录区间的最大值和最小值。

      以上就基本解决了统计答案一类的问题,那么如何寻找每个height影响的区间呢?

        很容易想到的就是对于每个height分别向左向右利用ST表二分它作为最小值的区间,注意值相等的情况,复杂度O(nlogn)。

        还有就是解决这类问题很经典的区间RMQ数据结构——笛卡尔树,可以实现O(n)解决问题。

      简单的说,笛卡尔树长得就像一棵treap,它既满足二分查找树的性质,又满足堆的性质。(假装大家都会treap)

      大约就可以脑补笛卡尔树是干什么用的了吧?

      如果还是不理解或是想知道构建方法的可以自行上网百度,小C这里就不多说了。

      

      建树的时候把每个height看做一个点(本来height很像是两点之间的线段,这里就很像是化边为点?)。

      而存储信息的时候每个节点存储的是后缀数组上的某段区间的信息,注意别混淆了。具体实现可以看小C的代码。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <ctime>
    #define l(a) (son[a][0])
    #define r(a) (son[a][1])
    #define ll long long
    #define INF 0x3FFFFFFF
    #define MN 300005
    #define MS 20
    using namespace std;
    int mp[MN],a[MN],sa[MS][MN],rak[MS][MN];
    ll ad[MN],an[MN],FINF;
    int son[MN][2],fa[MN],tp[MN],st[MN],top;
    int len[MN],w[MN],mn[MN],mx[MN];
    char c[MN];
    int n,p;
    int T;
    
    inline int read()
    {
        int n=0,f=1; char c=getchar();
        while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();}
        while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();}
        return n*f;
    }
    
    void mul(int* osa,int* ork,int* nsa,int* nrk,int k)
    {
        register int i;
        for (i=1;i<=n;++i) mp[ork[osa[i]]]=i;
        for (i=n;i;--i)
            if (osa[i]>k) nsa[mp[ork[osa[i]-k]]--]=osa[i]-k;
        for (i=n-k+1;i<=n;++i) nsa[mp[ork[i]]--]=i;
        for (i=1;i<=n;++i)
            nrk[nsa[i]]=nrk[nsa[i-1]]+(ork[nsa[i]]!=ork[nsa[i-1]]||ork[nsa[i]+k]!=ork[nsa[i-1]+k]);
    }
    
    void presa()
    {
        register int i,k;
        for (i=1;i<=n;++i) ++mp[a[i]];
        for (i=1;i<=26;++i) mp[i]+=mp[i-1];
        for (i=1;i<=n;++i) sa[p][mp[a[i]]--]=i;
        for (i=1;i<=n;++i) rak[p][sa[p][i]]=rak[p][sa[p][i-1]]+(a[sa[p][i-1]]!=a[sa[p][i]]);
        for (k=1;k<n;k<<=1,++p)    mul(sa[p],rak[p],sa[p+1],rak[p+1],k);
    }
    
    void dfs(int x)
    {    
        if (!x) return;
        dfs(l(x)); dfs(r(x));
        if (!l(x)) l(x)=MN-1,mn[l(x)]=mx[l(x)]=w[sa[p][rak[p][x]-1]],len[l(x)]=1;
        if (!r(x)) r(x)=MN-2,mn[r(x)]=mx[r(x)]=w[sa[p][rak[p][x]  ]],len[r(x)]=1;
        mn[x]=min(mn[l(x)],mn[r(x)]);
        mx[x]=max(mx[l(x)],mx[r(x)]);
        len[x]=len[l(x)]+len[r(x)];
        ad[tp[x]]+=1LL*len[l(x)]*len[r(x)];
        an[tp[x]]=max(an[tp[x]],1LL*mn[l(x)]*mn[r(x)]);
        an[tp[x]]=max(an[tp[x]],1LL*mn[l(x)]*mx[r(x)]);
        an[tp[x]]=max(an[tp[x]],1LL*mx[l(x)]*mn[r(x)]);
        an[tp[x]]=max(an[tp[x]],1LL*mx[l(x)]*mx[r(x)]);
    }
    
    int main()
    {
        register int i,k,x,y;
        n=read();
        scanf("%s",c+1);
        for (i=1;i<=n;++i) a[i]=c[i]-'a'+1;
        presa();
        for (i=1;i<=n;++i) w[i]=read();
        mn[0]=INF; mx[0]=-INF;
        memset(an,128,sizeof(an)); FINF=an[0]; tp[0]=-1;
        for (i=2;i<=n;++i)
            for (tp[sa[p][i]]=0,k=p,x=sa[p][i],y=sa[p][i-1];k>=0;--k)
                if (rak[k][x]==rak[k][y]) tp[sa[p][i]]+=(1<<k),x+=(1<<k),y+=(1<<k);
        for (i=2;i<=n;++i)
        {
            for (x=0;top&&tp[sa[p][i]]<=tp[st[top]];x=st[top--]);
            if (x) fa[x]=sa[p][i];
            l(sa[p][i])=x; r(st[top])=sa[p][i];  fa[sa[p][i]]=st[top]; st[++top]=sa[p][i];
        }
        dfs(st[1]);
        for (i=n-1;i>=0;--i) an[i]=max(an[i],an[i+1]),ad[i]+=ad[i+1];
        for (i=0;i<n;++i) printf("%lld %lld
    ",ad[i],an[i]==FINF?0:an[i]);
    }

      这个做法是小C之后才知道的,小C在这之前还自己YY了一个奇怪的线段树做法,思路好像完全不一样,洛谷上卡过了,BZOJ(时限0.5s)和自家OJ(未开O2)死活卡过不去几个点。代码姑且放出来留念吧。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define l(a) (a<<1)
    #define r(a) (a<<1|1)
    #define ll long long
    #define MM 1200005
    #define MN 300005
    #define MS 20
    using namespace std;
    int mp[MN],a[MN],sa[MS][MN],rak[MS][MN];
    ll ad[MM],an[MM],FINF;
    int len[MM],w[MN],mn[MM],mx[MM],inf,Finf;
    bool tg[MM];
    char c[MN];
    int n,p;
    
    inline int read()
    {
        int n=0,f=1; char c=getchar();
        while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();}
        while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();}
        return n*f;
    }
    
    void mul(int* osa,int* ork,int* nsa,int* nrk,int k)
    {
        register int i;
        for (i=1;i<=n;++i) mp[ork[osa[i]]]=i;
        for (i=n;i;--i)
            if (osa[i]>k) nsa[mp[ork[osa[i]-k]]--]=osa[i]-k;
        for (i=n-k+1;i<=n;++i) nsa[mp[ork[i]]--]=i;
        for (i=1;i<=n;++i)
            nrk[nsa[i]]=nrk[nsa[i-1]]+(ork[nsa[i]]!=ork[nsa[i-1]]||ork[nsa[i]+k]!=ork[nsa[i-1]+k]);
    }
    
    void presa()
    {
        register int i,k;
        for (i=1;i<=n;++i) ++mp[a[i]];
        for (i=1;i<=26;++i) mp[i]+=mp[i-1];
        for (i=1;i<=n;++i) sa[p][mp[a[i]]--]=i;
        for (i=1;i<=n;++i) rak[p][sa[p][i]]=rak[p][sa[p][i-1]]+(a[sa[p][i-1]]!=a[sa[p][i]]);
        for (k=1;k<n;k<<=1,++p)    mul(sa[p],rak[p],sa[p+1],rak[p+1],k);
    }
    
    void mark0(int x)
    {
        void down(int);
        if (tg[x]) {ad[x]+=1LL*len[x]*(len[x]-1)/2; len[x]=0; mn[x]=inf; mx[x]=Finf;}
        else {down(x); tg[x]=1;}
    }
    void enlen(int x,int z) {len[x]+=z;}
    void upmx(int x,int z)
    {
        if (mx[x]!=Finf) an[x]=max(an[x],1LL*mx[x]*z);
        if (mn[x]!=inf)  an[x]=max(an[x],1LL*mn[x]*z);
        mn[x]=min(mn[x],z); mx[x]=max(mx[x],z);
    }
    void down(int x)
    {
        if (tg[x]) {mark0(l(x)); mark0(r(x)); tg[x]=0;}
        if (len[x])
        {
            enlen(l(x),len[x]); enlen(r(x),len[x]);
            upmx(l(x),mx[x]); upmx(r(x),mx[x]);
            if (len[x]>1) {upmx(l(x),mn[x]); upmx(r(x),mn[x]);}
            len[x]=0; mn[x]=inf; mx[x]=Finf;
        }
        if (ad[x]) {ad[l(x)]+=ad[x]; ad[r(x)]+=ad[x]; ad[x]=0;}
        if (an[x]!=FINF) {an[l(x)]=max(an[l(x)],an[x]); an[r(x)]=max(an[r(x)],an[x]); an[x]=FINF;}
    }
    
    void write(int x,int L,int R)
    {
        if (L==R) {if (L<n) printf("%lld %lld
    ",ad[x],an[x]==FINF?0:an[x]); return;}
        down(x);
        int mid=L+R>>1;
        write(l(x),L,mid); write(r(x),mid+1,R);
    }
    
    void chzero(int x,int L,int R,int ql,int qr)
    {
        if (ql==L&&qr==R) {mark0(x); return;}
        down(x);
        int mid=L+R>>1;
        if (qr<=mid) chzero(l(x),L,mid,ql,qr);
        else if (ql>mid) chzero(r(x),mid+1,R,ql,qr);
        else {chzero(l(x),L,mid,ql,mid); chzero(r(x),mid+1,R,mid+1,qr);}
    }
    
    void modify(int x,int L,int R,int ql,int qr,int z)
    {
        if (ql==L&&qr==R) {enlen(x,1); upmx(x,z); return;}
        down(x);
        int mid=L+R>>1;
        if (qr<=mid) modify(l(x),L,mid,ql,qr,z);
        else if (ql>mid) modify(r(x),mid+1,R,ql,qr,z);
        else {modify(l(x),L,mid,ql,mid,z); modify(r(x),mid+1,R,mid+1,qr,z);}
    }
    
    int main()
    {
        register int i,tp,k,x,y;
        n=read();
        scanf("%s",c+1);
        for (i=1;i<=n;++i) a[i]=c[i]-'a'+1;
        for (i=1;i<=n;++i) w[i]=read();
        presa(); 
        memset(an,128,sizeof(an)); FINF=an[0];
        memset(mn,127,sizeof(mn)); inf=mn[0];
        memset(mx,128,sizeof(mx)); Finf=mx[0];
        chzero(1,0,n,0,n);
        for (i=1;i<=n;++i)
        {
            for (tp=0,k=p,x=sa[p][i],y=sa[p][i-1];k>=0;--k)
                if (rak[k][x]==rak[k][y]) tp+=(1<<k),x+=(1<<k),y+=(1<<k);
            if (tp<n) chzero(1,0,n,tp+1,n);
            modify(1,0,n,0,n,w[sa[p][i]]);
        }
        chzero(1,0,n,0,n);
        write(1,0,n);
    }
    View Code

    Last Word

      看到最后你知道小C一定又要吐槽了。(废话,小C发哪篇文章不是为了吐槽?)

      做这题之前小C还从来没有写过后缀数组,上网找了一篇“公认”很优秀的后缀数组模板(这里不是指黄学长的)讲解,理解了半天,愣是没理解。

      奇怪,这大概是小C唯一一个看着模板还依然没有思路的算法了吧。(明明是你智商比较低)

      然后点开黄学长的博客,发现了画风迥异的后缀数组模板,小C当机立断就写这种风格的了。

      虽然黄学长写得十分优雅,但感觉还是无法拯救自己的智商,全程抄代码,至今好像还有点蒙。(CTSC考试之前一直都在看后缀数组模板QAQ,还好没考

      这是其一。然后就是自己脑子一抽写了个线段树(败给了常数),结果学写了正解之后发现自己想不出原来的线段树做法了有木有?

      写正解的时候也处处碰壁,直到遇到小D和小F两位大神的指(yi)点(tong)迷(nu)津(D),才理解可以把height理解为连接后缀数组的线段,思路瞬间清晰。

      蒟 蒻 求 轻 D

  • 相关阅读:
    MySQL学习笔记
    为什么使用多态?实现多态的有哪些方式
    C++中派生类使用基类成员的问题
    实现Modbus ASCII多主站应用
    PHP第四天 函数引用传值
    Webpack 快速上手(下)
    Java---23种设计模式(九)------组合模式
    【笔记】Linux就该这么学-第六课第四章
    Node.js HTTPS
    Node.js Smalloc
  • 原文地址:https://www.cnblogs.com/ACMLCZH/p/6840529.html
Copyright © 2020-2023  润新知