【LuoguP4143】采集矿石(后缀数组求解子串字典序)

题目链接

题意

给出一个字符串,每一个位置上的字符有一个权值,统计满足如下条件的子串个数:

它包含字符的权值和等于它在所有本质不同的子串中的字典倒序

Sol

首先暴力的思路就是把每一个串抠出来sort,直接算.

然后所有字符相同就是方便了我们处理字典序,在写这一档分时,我们应该是枚据一个左端点,然后二分右端点

为什么呢?因为当字符相同时字典序只和长度有关,右端点每往右移动一下,字典序一定是下降的,但是权值和却不会下降,因此他们的差值是单调的,可以把0点给二分出来

这启发了我们的正解,不难可以发现,即使字符不相同,右端点往右移动一次,相对于之前来说是在末尾添了一个字符,字典序也是只会下降的,所以我们现在要做的就是如何快速求出一段子串的排名,这样我们枚举左端点,二分右端点就能统计答案了(题中答案不超过2e5也是由于每一个左端点最多产生1的贡献)

怎么计算一个子串的排名呢?

考虑后缀数组

先来看一个暴力之后的例子:如一个串abacc,把它的子串排序后全部写出来就是:
a
a
ab
aba
abac
abacc
ac
acc
b
ba
bac
bacc
c
c
cc

用斜体标注出来的是原串的一个后缀

我们可以很显然发现,一个串的所有子串排序之后形成的序列一定是一段子串,然后一个后缀,然后再是一堆子串(可能没有,但不影响),并且一个后缀之前到上一个排名的后缀直接的子串一定是该后缀的一个前缀

其实这也不难理解,回想一下我们人工排序时选择子串的顺序,肯定是第一个字符小的字典序要小,
这样一直选下去=就一定会选到一个后缀,并且之前选择的都是他的前缀

但是这个有什么用呢,其实有了这个性质,我们很容易得出两个排名相邻的后缀的排名之间的子串个数,在没有lcp的情况下这个个数就等于后面那个后缀的长度,而有lcp就把height减掉就可以了(本质可以相同的情况下就等于长度)

并且字典序最小的后缀在整个子串中的排名一定是他的长度,于是我们可以预处理出每一个后缀的在所有本质不同的子串中的排名

于是只要考虑怎么求出任意一个子串的排名,根据上面的性质,如果这个子串是某一个后缀的一个前缀的话可能比较好办,于是考虑以这个串左端点为起点的后缀

然后就很容易发现两种情况
假设这个子串长度大于该后缀的height,那么直接用当前这个后缀的排名减去他们之间的长度差就可以了,因为这样是一直往上都是前缀的

但是为什么height大于等于串长就不对?因为这时很显然它不仅是这个后缀的一个前缀,它还是前面那个后缀的一个前缀,很简单发现它的字典序应该比前面的那个后缀还要小,自然就不能这么算了,那怎么办呢?

虽然不能用这个后缀,但是两个排名相邻后缀之间的子串是后面那个后缀的前缀这个性质还是没有变的,我们只要找到一个排名最靠前面的包含这个子串的后缀进行计算就一定没有问题了

并且后缀数组的一个优良性质: 两个后缀的lcp等于他们排名之间的后缀的height的最小值

那么这个显然就也是单调的了,也可以二分一下,找到那个和当前后缀的lcp大于等于当前子串长度的最靠前的排名就行了

复杂度 O(nlog2n) O ( n l o g 2 n )
实现还是比较简单的(注意这里是字典倒序,要用总的本质不同的子串减掉某串的字典序加1才是字典倒序)

代码:

#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int N=2e5+10;
typedef long long ll;
char s[N];int n;
ll val[N];int m;
namespace SA{
    typedef pair<int,int> Pr;
    int sa[N],height[N],rank[N];
    int wa[N],wb[N],ws[N];
    int st[30][N];ll S=0;int ans=0;
    Pr Ans[N];
    inline bool cmp(int i,int j,int d,int *Rk){return (Rk[i]==Rk[j]&&Rk[i+d]==Rk[j+d]);}
    void get_sa(){
        register int i,p,d;
        for(i=1;i<=n;++i) ++ws[wa[i]=s[i]];
        for(i=1;i<=m;++i) ws[i]+=ws[i-1];
        for(i=n;i;--i) sa[ws[wa[i]]--]=i;
        for(d=1,p=0;p1){
            for(p=0,i=n-d+1;i<=n;++i) wb[++p]=i;
            for(i=1;i<=n;++i) if(sa[i]>d) wb[++p]=sa[i]-d;
            for(i=1;i<=m;++i) ws[i]=0;
            for(i=1;i<=n;++i) ++ws[wa[wb[i]]];
            for(i=1;i<=m;++i) ws[i]+=ws[i-1];
            for(i=n;i;--i) sa[ws[wa[wb[i]]]--]=wb[i];
            swap(wa,wb);wa[sa[p=1]]=1;
            for(i=2;i<=n;++i) wa[sa[i]]=cmp(sa[i-1],sa[i],d,wb)? p:++p;
        }
        return;
    }
    void get_height(){
        register int i,j,k=0;
        for(i=1;i<=n;++i) rank[sa[i]]=i;
        for(i=1;i<=n;height[rank[i++]]=k)
            for(k? --k:0,j=sa[rank[i]-1];s[i+k]==s[j+k];++k);
        return;
    }
    int log[N];
    void get_st(){
        for(register int i=1;i<=n;++i) st[0][i]=height[i];
        for(register int i=1;i<=n;++i) if((1<<log[i-1])log[i]=log[i-1]+1;else log[i]=log[i-1];
        for(register int k=1;k<=log[n];++k)
            for(register int i=1;i+(1<1<=n;++i){
                st[k][i]=min(st[k-1][i],st[k-1][i+(1<1)]);
            }
        return;
    }
    ll rk[N];
    void get_rk(){//增量构造
        rk[sa[1]]=n-sa[1]+1;
        for(register int i=2;i<=n;++i){
            rk[sa[i]]=rk[sa[i-1]]-height[i]+(n-sa[i]+1);
        }
        return;
    }
    void Prepare(){
        m=200;get_sa();get_height();get_st();
        S=1ll*(n)*(n+1)/2;for(register int i=1;i<=n;++i) S-=height[i];//本质不同的子串个数(最大排名)
        get_rk();//计算每一个后缀在所有本质不同的子串中的排名
    }
    inline int query(int l,int r){
        if(l>r) swap(l,r);++l;
        if(l==r) return st[0][l];
        register int D=log[r-l+1]-1;
        return min(st[D][l],st[D][r-(1<1]);
    }
    inline ll calc(int l,int r){//算一个子串的排名
        register int len=r-l+1;
        if(len>height[rank[l]]) return (S-(rk[l]-(n-l+1-len))+1);
        register int ql=1,qr=rank[l]-1;
        register int p=0;
        while(ql<=qr){
            register int mid=ql+qr>>1;
            if(query(mid,rank[l])>=len) p=mid,qr=mid-1;
            else ql=mid+1;
        }
        register int leq=n-sa[p]+1;
        return (S-(rk[sa[p]]-(leq-len))+1);
    }
    void work(){
        //rk 递降 val不降,实际上是任何一个情况都是满足可二分性的
        for(register int i=1;i<=n;++i){
            register int l=i,r=n;
            while(l<=r) {
                register int mid=l+r>>1;
                register ll Rk=calc(i,mid);//算出这一个子串的排名
                register ll Sum=val[mid]-val[i-1];//权值和
                if(Sum>Rk) r=mid-1;//val大于rk
                else if(Sum1;//val小于rk
                else {Ans[++ans]=Pr(i,mid);break;}
            }
        }
        printf("%d\n",ans);
        for(register int i=1;i<=ans;++i) printf("%d %d\n",Ans[i].first,Ans[i].second);
        return;
    }
}
int main()
{
    scanf("%s",s+1);n=strlen(s+1);s[n+1]='0';
    for(register int i=1;i<=n;++i) scanf("%lld",&val[i]),val[i]+=val[i-1];
    SA::Prepare();SA::work();
}

你可能感兴趣的:(======题解======,===字符串===,后缀数组)