【HAOI2016/BZOJ4566】找相同字符 后缀数组+单调栈

原题走这里

鉴于我实在不是很懂单调栈和单调队列这一系列东西,所以我决定稍微具体讲一下单调栈。

恩,本题实质上就是求两个字符串的公共子串数,其中只要出现位置不同,就算是不同的子串。
处理多个字符串的经典套路:把两个字符串连在一起,中间用分割符分割。
于是问题就转化为了:求分隔符前后都出现过的子串个数。
子串就是后缀的前缀,于是问题又转化成了:求整个串中,任意两个后缀的LCP之和,这两个子串要一个在分割符前,一个在分割符后。

恩,求后缀的LCP我们可以用后缀数组中的height数组,两个后缀的LCP长度就是height上的区间最小值。
于是我们要求的实际上就是:

SA[i]<n+1<SA[j]minikjheight[k]+SA[j]<n+1<SA[i]minikjheight[k] ∑ S A [ i ] < n + 1 < S A [ j ] min i ≤ k ≤ j h e i g h t [ k ] + ∑ S A [ j ] < n + 1 < S A [ i ] min i ≤ k ≤ j h e i g h t [ k ]

其中SA是排名第 i i 的后缀的编号。
让我们姑且只考虑左边,一个明显的暴力解法是枚举 i,j i , j 然后RMQ求出区间最小值。
然而这个解法明显不符合时间复杂度的要求,因此我们的方法有待改进。

首先我们发现,右端点固定的一系列区间最小值,能够构成一个单调增的序列,其中相同的值可以合并。
如果我们不断将右端点右移,则序列末端的一些值会被更新为同一个值,可以将这几个值合并,相当于这几个值被从队列末端弹掉了。
与此同时如果你又扫到了某一个 SA[i]<n+1 S A [ i ] < n + 1 i i ,你又要把它加进序列的末端。
这相当于一个栈的结构,而其中的元素又是单调的,就是所谓的单调栈。

于是,我们可以使用单调栈,求出式子的值即可,时间复杂度为 O(n) O ( n ) ,加上构造后缀数组,共 O(nlogn) O ( n l o g n )

诸君,我讨厌单调栈。

讲真,一开始,我的解法是二分,然后二分合并左右两边结果的时候用的又是单调栈,然后还AC了,后来看题解才发现,只用单调栈就可以了。

具体详见代码如下:

// luogu-judger-enable-o2
#include 
#define LL long long
using namespace std;
int n,len,SA[400010],r[400010],tp[400010],t[400010],h[400010],st[400010],s[400010],top;
char ch[400010];
void Sort(int m) {
    memset(t,0,sizeof(int)*(m+1));
    for(int i=1; i<=len; i++) {
        t[r[i]]++;
    }
    for(int i=1; i<=m; i++) {
        t[i]+=t[i-1];
    }
    for(int i=len; i; i--) {
        SA[t[r[tp[i]]]--]=tp[i];
    }
}
void build_SA() {
    Sort(127);
    for(int i=1,p=0,m=127; i<=len&&p1,m=p) {
        p=0; 
        for(int j=len-i+1; j<=len; j++) {
            tp[++p]=j;
        }
        for(int j=1; j<=len; j++) {
            if(SA[j]>i) {
                tp[++p]=SA[j]-i;
            }
        }
        Sort(m);
        swap(r,tp);
        r[SA[1]]=p=1;
        for(int j=2; j<=len; j++) {
            r[SA[j]]=((tp[SA[j]]==tp[SA[j-1]])&&(tp[SA[j]+i]==tp[SA[j-1]+i])?p:++p);
        }
    }
    for(int i=1,j,k=0; i<=len; h[r[i++]]=k) {
        for(k?k--:0,j=SA[r[i]-1]; ch[i+k]==ch[j+k]; k++);
    }
}
LL solve(bool b) {
    LL ret=0,temp=0;
    top=0; 
    for(int i=len,j=0; i; i--,j=0) {
        if((SA[i]>n)^b){
            ret+=temp;
        }
        while(st[top]>h[i]&&top)
        {
            temp-=1LL*s[top]*st[top];
            j+=s[top--];
        }
        st[++top]=h[i];
        s[top]=j;
        if((SA[i]<=n)^b) { //向单调栈内插入元素
            s[top]++;
        } 
        temp+=1LL*s[top]*st[top];
    }
    return ret;
}
int main() {
    scanf("%s",ch+1);
    n=strlen(ch+1);
    ch[n+1]='$';
    scanf("%s",ch+n+2);
    len=strlen(ch+1);
    for(int i=1;i<=len;i++)
    {
        tp[i]=i;
        r[i]=ch[i];
    }
    build_SA();
    cout<0)+solve(1)<return 0;
}

你可能感兴趣的:(【HAOI2016/BZOJ4566】找相同字符 后缀数组+单调栈)