【Lyndon划分】【后缀数组】【构造】正睿NOI2019集训day6T3 简单字符串

题意

对于一个串S,定义\(f(S,k)\)为将S划分为至多k段,假设分别为\(u_1,u_2,u_3...u_k\),取按照字典序比较意义下的最大的一个串为\(u_j\),那么\(f(S,k)=u_j\)

现在给定一个串S,并给出q个询问,每次询问给出\(l,k\),需要你求出\(f(S[l,l+1,l+2...,n],k)\)的值。假若答案为\(f(S[l,l+1,l+2,...,n],k)=S[a_i,a_i+1,a_i+2,...,b_i]\),那么你只需要输出\(a_i,b_i\)就可以了。假若存在多组最优解,你需要输出\(a_i\)最小的那组解。

输入格式

第一行一个串S,一个整数q,意义如上。
接下来q行,每行两个整数分别表示\(l,k\)

数据范围

对于30%的数据,\(|S|,q<=50\)
对于60%的数据,\(|S|<=8000,q<=50\)
对于100%的数据,\(|S|,q<=100000,1<=l,k<=|S|\)

样例输入

abaababa 2
1 4
3 2

样例输出

1 2
3 7

思路

考场上只会一个30分的骗分做法。tcl...

先说说这个暴力吧。主要是全都做第一题去了,导致这道题并没有太多的时间想,只好打自己最开始想的那个暴力,并没有花太多的时间来思考。

大致就是一个\(n^5\)但是跑不满的DP。定义\(dp[i][j][p][q]\)为已经从后往前截取了i段,并且当前位置在j,截取的段中最大的串的起始位置在p,长度为q,这样的方案是否可行,也就是一个bool数组。然后\(O(n^4)\)枚举状态\(O(n)\)转移过后,每次询问时花\(O(n^2)\)枚举哪个串是最长串就可以了。
这个思路十分的不优秀,也和正解没有半毛钱关系...

下面进入正题。
首先要提到的是一个NOI2019冬令营营员交流讲过的东西,叫做Lyndon串。具体的细节我也不是很懂,这里只是大致的提一下。

定义一个串S为Lyndon串,当且仅当满足在将这个串进行循环移位之后形成的串的集合中,S是严格最小的一个。

然后定义一个串S的Lyndon划分为:分成k段,假设分别为:\(U_1,U_2,U_3...U_k\),其中\(U_i\)均为Lyndon串,且在字典序比较的意义下,\(U_1>=U_2>=U_3>=U_4...>=U_k\)

考虑对于一个字符串S如何构造这样一个Lyndon划分(这里的细节搞得不是很清楚,也就是不清楚这样做为什么是对的)。考虑从后往前加入字符,假设当前的状态为已经有了k个串:\(U_1,U_2,...U_k\),其中,U1是最靠前的一个,然后我们要加入的是一个新的子串\(Z\)

那么你首先判一下,Z是否是等于U1的,如果是,则将U1上面的计数器cnt++,删除Z。否则,如果说Z<U1的话,你就把Z和U1连接起来,作为新的U1;如果Z>U1的话,你就把Z作为新的U1,k++。至于枚举到第i个位置,要插入S[i]时,你就将S[I]视作一个单独的字符串,塞到存\(U_{1,2,3...k}\)的那个队列里面一直弹就可以了。

这样我们就成功构造出了一个Lyndon划分,至于判断相等或者是大于小于的部分,就用后缀数组搞一下就可以了。

接下来考虑如何用这个Lyndon划分来构造我们的最终的答案。

首先一定不会切在Lyndon划分断点之外的地方。因为一个Lyndon串本身就是最小的了,加入是在一个Lyndon子串\(U\)中间切的,那么无论U后面是什么串,都相当于是进行了循环移位(可以自己画个图想一想),那么根据Lyndon串的定义,形成的新串是严格小于\(U\)本身的。所以说必然是在Lyndon划分的断点上面切。

然后就是假设整个Lyndon子串的序列是:
\[U_1^{k_1},U_2^{k_2},U_3^{k_3}...U_z^{k_z}\]
那么必定不会在\(U_1^{k_1}\)的后面进行切割。因为考虑是取字典序最大的那一个,那么在后面切了之后并不会形成任何新的比以U1开头的更大的串,所以说只是拜拜浪费我们切割的次数,不优。

然后整个情况就变成了只能够在\(U_1^{k_1}\)中间切割了。假设最终要分成\(J\)端(注意可能和上面的变量名不一样了...是分了两天写的这篇博客...),下面分两种情况进行讨论:

首先是\(k_1\ mod\ J \neq 0\)的情况。大致想一下可以发现,肯定是尽量分的越平均越好,所以说,我们将前面分成\(k_1\ mod\ J\)段包含\(\lceil \frac{k_1}{J}\rceil\)\(U_1\)串,剩下的段分别都包含\(\lfloor\frac{k_1}{J}\rfloor\)\(U_1\)。注意的是,最后一段会和后面的\(U_2^{k_2},U_3^{k_3}...U_z^{k_z}\)接起来,但是并没有影响,因为\(U_{2至z}\)都是<\(U_1\)的。那么这样子我们的答案传就是\(\lceil \frac{k_1}{J}\rceil\)\(U_1\)接起来的串。

然后是\(k_1\ mod\ J = 0\)的情况。初步的想法是切成若干个包含\(\frac{k_1}{J}\)\(U_1\)的段。但是我们仔细想一下,因为这个时候每一段包含的串的数量都相等了,所以不能简单的像上一种情况那样思考了。说起来有些麻烦,下面直接举个例子可能你就理解了:

假设\(k_1=4,J=2\)
两种构造方式
第一种情况显然是后面那个串为最终的答案串,第二种情况显然是前面那个串为最终的答案串。
因为第一种情况的后面跟的是小的串,所以显然是更优秀的,因此当\(k_1\ mod\ J = 0\)时,都是像第一种那样的构造的。

这里还需要注意的是,当整个串都只有\(U_1\)这一种Lyndon串时,根据题目的要求应该取第一种情况中的前面的那个串就可以了。

这样子就构造完了,希望对你有一些帮助!

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 100000
using namespace std;
struct node
{
    int pos,len,siz;
    node(){};
    node(int _pos,int _len,int _siz):pos(_pos),len(_len),siz(_siz){};
}stk[MAXN+5];
char S[MAXN+5];
int q,n,t,array[4][MAXN+5],*sa,*nsa,*rk,*nrk;
int height[MAXN+5],bin[MAXN+5],st[MAXN+5][20];
int topUlen[MAXN+5],topK[MAXN+5];
void Calc_Sa()
{
    sa=array[0],nsa=array[1],rk=array[2],nrk=array[3];
    for(int i=1;i<=n;i++)
        bin[(int)S[i]]++;
    for(int i=1;i<=MAXN;i++)
        bin[i]+=bin[i-1];
    for(int i=1;i<=n;i++)
        sa[bin[(int)S[i]]--]=i;
    rk[sa[1]]=1;
    for(int i=2;i<=n;i++)
    {
        rk[sa[i]]=rk[sa[i-1]];
        if(S[sa[i]]!=S[sa[i-1]])
            rk[sa[i]]++;
    }
    for(int k=1;k<=n&&rk[sa[n]]<n;k<<=1)
    {
        for(int i=1;i<=n;i++)
            bin[rk[sa[i]]]=i;
        for(int i=n;i>=1;i--)
            if(sa[i]-k>=1)
                nsa[bin[rk[sa[i]-k]]--]=sa[i]-k;
        for(int i=n-k+1;i<=n;i++)
            nsa[bin[rk[i]]--]=i;
        nrk[nsa[1]]=1;
        for(int i=2;i<=n;i++)
        {
            nrk[nsa[i]]=nrk[nsa[i-1]];
            if(rk[nsa[i]]!=rk[nsa[i-1]]||rk[nsa[i]+k]!=rk[nsa[i-1]+k])
                nrk[nsa[i]]++;
        }
        swap(nsa,sa);
        swap(nrk,rk);
    }
}
void Calc_Height()
{
    int k=0;
    for(int i=1;i<=n;i++)
        if(rk[i]==1)
            height[rk[i]]=1;
        else
        {
            for(int j=sa[rk[i]-1];i+k<=n&&j+k<=n&&S[i+k]==S[j+k];k++);
            height[rk[i]]=k;
            k=max(k-1,0);
        }
}
void Init()
{
    for(int i=1;i<=n;i++)
        st[i][0]=height[i];
    for(int j=1;(1<<j)<=n;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
int RMQ(int L,int R)
{
    int LOG=0;
    while((1<<LOG)<=R-L+1)
        LOG++;
    LOG--;
    return min(st[L][LOG],st[R-(1<<LOG)+1][LOG]);
}
int LCP(int p1,int p2)
{
    if(p1==p2)  return n-p1+1;
    int L=min(rk[p1],rk[p2])+1;
    int R=max(rk[p1],rk[p2]);
    return RMQ(L,R);
}
void DeBug()
{
    printf("==============\n");
    for(int i=t;i>=1;i--)
        printf("(%d %d %d)\n",stk[i].pos,stk[i].len,stk[i].siz);
    printf("==============\n");
}
void Lyndon_Partition()
{
    t=0;
    for(int i=n;i>=1;i--)
    {
        stk[++t]=node(i,1,1);
        while(t>=2)
        {
            node tmp1=stk[t],tmp2=stk[t-1];
            int len=LCP(tmp1.pos,tmp2.pos);
            if(len>=tmp1.len&&len>=tmp2.len&&tmp1.len==tmp2.len)
                stk[--t]=node(tmp1.pos,tmp1.len,tmp1.siz+tmp2.siz);
            else if(rk[tmp1.pos]<rk[tmp2.pos])
                stk[--t]=node(tmp1.pos,tmp1.len+tmp2.len*tmp2.siz,1);
            else
                break;
        }
        topUlen[i]=stk[t].len;
        topK[i]=stk[t].siz;
//      DeBug();
    }
}
int main()
{
    scanf("%s %d",S+1,&q);
    n=strlen(S+1);
    Calc_Sa();
    Calc_Height();
//  for(int i=1;i<=n;i++)
//      printf("sa[%d]:%d\n",i,sa[i]);
//  for(int i=1;i<=n;i++)
//      printf("rk[%d]:%d\n",i,rk[i]);
    Init();
    Lyndon_Partition();
    for(int i=1;i<=q;i++)
    {
        int l,k;
        scanf("%d %d",&l,&k);
        if(topK[l]%k!=0)
        {
            int len=(topK[l]+k-1)/k;
            printf("%d %d\n",l,l+len*topUlen[l]-1);
        }
        else
        {
            int len=topK[l]/k;
            int bg=l+topUlen[l]*(topK[l]-len);
            if(l+topUlen[l]*topK[l]-1==n)
                printf("%d %d\n",l,l+topUlen[l]*len-1);
            else
                printf("%d %d\n",bg,n);
        }
    }
    return 0;
}
/*
acccdaccbbbcdddcdacaaadadbdddaababbdccdbdcababbcca 1
48 2

*/

猜你喜欢

转载自www.cnblogs.com/T-Y-P-E/p/11166619.html