字尾陣列 學習筆記

卡布叻_周深發表於2024-04-28

理論知識

詳見 OI Wiki

模板

字尾排序

一切有關字尾陣列問題的必備板子。

求字尾陣列模板題,OI Wiki 有詳解 。

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e6+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int n,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N];
char s[N];
void count_sort(int n,int m)
{
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        cnt[key[i]]++;
    for(int i=1;i<=m;i++)   
        cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)
        sa[cnt[key[i]]]=id[i],
        cnt[key[i]]--;
}
void init()
{
    int m=127,tot=0,num=0;
    for(int i=1;i<=n;i++)
        rk[i]=(int)s[i],
        id[i]=i,
        key[i]=rk[id[i]];
    count_sort(n,m);
    for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
    {
        num=0;
        for(int i=n;i>=n-w+1;i--)
            num++,
            id[num]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>w)
                num++,
                id[num]=sa[i]-w;
        for(int i=1;i<=n;i++)
            key[i]=rk[id[i]];
        count_sort(n,m);
        for(int i=1;i<=n;i++)
            oldrk[i]=rk[i];
        tot=0;
        for(int i=1;i<=n;i++)
            tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
            rk[sa[i]]=tot;
    }
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    cin>>s+1;
    n=strlen(s+1);
    init();
    for(int i=1;i<=n;i++)
        cout<<sa[i]<<' ';
}

可重疊最長重複子串

\(height\) 陣列模板題,OI Wiki 有詳解如何求 \(height\) 陣列。

即求 \(\max\limits_{i=1}^n\{height_i\}\)

注意 \(height_1=0\)

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int n,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],ans;
char s[N];
void count_sort(int n,int m)
{
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        cnt[key[i]]++;
    for(int i=1;i<=m;i++)   
        cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)
        sa[cnt[key[i]]]=id[i],
        cnt[key[i]]--;
}
void init()
{
    int m=127,tot=0,num=0;
    for(int i=1;i<=n;i++)
        rk[i]=(int)s[i],
        id[i]=i,
        key[i]=rk[id[i]];
    count_sort(n,m);
    for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
    {
        num=0;
        for(int i=n;i>=n-w+1;i--)
            num++,
            id[num]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>w)
                num++,
                id[num]=sa[i]-w;
        for(int i=1;i<=n;i++)
            key[i]=rk[id[i]];
        count_sort(n,m);
        for(int i=1;i<=n;i++)
            oldrk[i]=rk[i];
        tot=0;
        for(int i=1;i<=n;i++)
            tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
            rk[sa[i]]=tot;
    }
    for(int i=1,k=0;i<=n;i++)
    {
        if(rk[i]==0) continue;
        if(k) k--;
        while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
        height[rk[i]]=k;
    }
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n);
    cin>>s+1;
    init();
    for(int i=1;i<=n;i++)
        ans=max(ans,height[i]);
    cout<<ans;
}

應用

\(height\) 陣列基本應用

不同子串個數

所有子串的個數為 \(\dfrac{n(n+1)}{2}\) ,其中重複的有 \(\sum\limits_{i=1}^nheight_i\) ,故答案為 \(\dfrac{n(n+1)}{2}-\sum\limits_{i=1}^nheight_i\)

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int n,q,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],ans;
char s[N];
int sta[N],top,a[N],b[N];
struct aa
{
    int l,r,id;
}e[N];
bool cmp(aa a,aa b) {return a.l==b.l?a.r>b.r:a.l<b.l;}
void count_sort(int n,int m)
{
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        cnt[key[i]]++;
    for(int i=1;i<=m;i++)   
        cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)
        sa[cnt[key[i]]]=id[i],
        cnt[key[i]]--;
}
void init()
{
    int m=127,tot=0,num=0;
    for(int i=1;i<=n;i++)
        rk[i]=(int)s[i],
        id[i]=i,
        key[i]=rk[id[i]];
    count_sort(n,m);
    for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
    {
        num=0;
        for(int i=n;i>=n-w+1;i--)
            num++,
            id[num]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>w)
                num++,
                id[num]=sa[i]-w;
        for(int i=1;i<=n;i++)
            key[i]=rk[id[i]];
        count_sort(n,m);
        for(int i=1;i<=n;i++)
            oldrk[i]=rk[i];
        tot=0;
        for(int i=1;i<=n;i++)
            tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
            rk[sa[i]]=tot;
    }
    for(int i=1,k=0;i<=n;i++)
    {
        if(rk[i]==0) continue;
        if(k) k--;
        while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
        height[rk[i]]=k;
    }
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n);
    cin>>s+1;
    init();
    for(int i=1;i<=n;i++)
        ans+=height[i];
    cout<<n*(n+1)/2-ans;
}

Milk Patterns G

求出現最少 \(k\) 次的子串的最大長度。

出現至少 \(k\) 次說明字尾排序後至少有連續 \(k\) 個字尾以這個子串為公共串。

即求出沒連續 \(k-1\)\(height_i\) 的最小值,這些最小值中的最大值即為所求。

可以用單調佇列 \(O(n)\) 解決,當然及時不用也足以 \(AC\)

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e6+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,K,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],ans;
int s[N];
deque<int>q;
void count_sort(int n,int m)
{
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        cnt[key[i]]++;
    for(int i=1;i<=m;i++)   
        cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)
        sa[cnt[key[i]]]=id[i],
        cnt[key[i]]--;
}
void init()
{
    int m=1000000,tot=0,num=0;
    for(int i=1;i<=n;i++)
        rk[i]=s[i],
        id[i]=i,
        key[i]=rk[id[i]];
    count_sort(n,m);
    for(int w=1;tot!=n;w<<=1,m=tot)
    {
        num=0;
        for(int i=n;i>=n-w+1;i--)
            num++,
            id[num]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>w)
                num++,
                id[num]=sa[i]-w;
        for(int i=1;i<=n;i++)
            key[i]=rk[id[i]];
        count_sort(n,m);
        for(int i=1;i<=n;i++)
            oldrk[i]=rk[i];
        tot=0;
        for(int i=1;i<=n;i++)
            tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
            rk[sa[i]]=tot;
    }
    for(int i=1,k=0;i<=n;i++)
    {
        if(rk[i]==0) continue;
        if(k) k--;
        while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
        height[rk[i]]=k;
    }
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n),read(K);
    K--;
    for(int i=1;i<=n;i++) read(s[i]);
    init();
    for(int i=1;i<=n;i++)
    {
        while(!q.empty()&&height[q.front()]>=height[i]) q.pop_front();
        q.push_front(i);
        while(!q.empty()&&q.back()<=i-K) q.pop_back();
        if(i>=K) ans=max(ans,height[q.back()]);
    }
    write(ans);
}

結合 \(ST\)

我們知道 \(LCP(sa_l,sa_r)=\min\limits_{i=l+1}^r\{height_i\}\) ,所以透過 \(ST\) 表維護 \(RMQ\) 是很常見的。

很多時候 \(ST\) 表起的只是一個輔助作用,所以好多結合 \(ST\) 表的題還會結合別的。

Yet Another LCP Problem

對於每一組給定的 \(a,b\) 陣列,不妨將其拼成 \(c\) 陣列。

我們定義 \(ans_x=\sum\limits_{i=1}^{len_x}\sum\limits_{j=1}^{len_x}LCP(s_{x_i\sim n},s_{x_j\sim n})\) ,那麼有:

\[\sum\limits_{i = 1}^{i = k} \sum\limits_{j = 1}^{j = l}{LCP(s_{a_i\sim n}, s_{b_j \sim n})}=\dfrac{ans_c-ans_a-ans_b}{2} \]

至於為什麼 \(\dfrac{1}{2}\) 如下:

image

我們知道 \(\sum\limits_{i=1}^{len_x}\sum\limits_{j=i+1}^{len_x}LCP(s_{x_i\sim n},s_{x_j\sim n})=\dfrac{\sum\limits_{i=1}^{len_x}\sum\limits_{j=1}^{len_x}LCP(s_{x_i\sim n},s_{x_j\sim n})}{2}\) ,不妨直接另 \(ans_x=\sum\limits_{i=1}^{len_x}\sum\limits_{j=i+1}^{len_x}LCP(s_{x_i\sim n},s_{x_j\sim n})\) ,那麼求 \(ans_c-ans_a-ans_b\) 即可。

結合單調棧求 \(ans_x\)

先將 \(x\) 陣列按照 \(rk[x_1]<rk[x_2]\) 排序,使其符合單調性。

定義 \(val_i=LCP(x_{i-1},x_i)\) ,類似 \(luogu~P4248\) 差異 這道題,去計算 \(val_i\) 的貢獻,實際上 \(val\) 的定義和 \(height\) 是類似的,因為 \(x_i\) 並不連續,所以需要這樣操作,顯然有 \(val_1=0\)

接下來單調棧維護的過程與 \(luogu~P4248\) 差異 是類似的,處理出最靠右的 \(0\leq l<i,val_l<val_i\) 的位置,以及最靠左的 \(i<r\leq n,val_r<val_i\) 的位置,那麼 \(l\sim r\) 這一段的 \(LCP\) 均為 \(val_i\) ,依據乘法原理, \(val_i\) 的貢獻就是 \(val_i\times (i-l)\times (r-i)\) ,不妨定義 \(l_i=i-l,r_i=r-i\)

那麼 \(ans_x=\sum\limits_{i=1}^{len_x}val_i\times l_i\times r_i\)

至於 \(LCP\) ,可以用 \(ST\) 表維護。

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=4e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int n,m,ans,sum,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],l[N],r[N],a[N],b[N],c[N],mi[N][30],val[N];
char s[N],t[N];
int sta[N],top;
void clean()
{
    memset(rk,0,sizeof(rk));
    memset(sa,0,sizeof(sa));
    memset(id,0,sizeof(id));
    memset(oldrk,0,sizeof(rk));
    memset(cnt,0,sizeof(cnt));
    memset(key,0,sizeof(key));
    memset(height,0,sizeof(height));
    memset(l,0,sizeof(l));
    memset(r,0,sizeof(r));
}
void count_sort(int n,int m)
{
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        cnt[key[i]]++;
    for(int i=1;i<=m;i++)   
        cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)
        sa[cnt[key[i]]]=id[i],
        cnt[key[i]]--;
}
void init(char s[],int n)
{
    int m=127,tot=0,num=0;
    for(int i=1;i<=n;i++)
        rk[i]=(int)s[i],
        id[i]=i,
        key[i]=rk[id[i]];
    count_sort(n,m);
    for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
    {
        num=0;
        for(int i=n;i>=n-w+1;i--)
            num++,
            id[num]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>w)
                num++,
                id[num]=sa[i]-w;
        for(int i=1;i<=n;i++)
            key[i]=rk[id[i]];
        count_sort(n,m);
        for(int i=1;i<=n;i++)
            oldrk[i]=rk[i];
        tot=0;
        for(int i=1;i<=n;i++)
            tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
            rk[sa[i]]=tot;
    }
    for(int i=1,k=0;i<=n;i++)
    {
        if(rk[i]==0) continue;
        if(k) k--;
        while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
        height[rk[i]]=k;
    }
}
void RMQ()
{
    memset(mi,0x3f,sizeof(mi));
    for(int i=1;i<=n;i++)
        mi[i][0]=height[i];
    for(int j=1;j<=log2(n);j++)
        for(int i=1;i<=n-(1<<j)+1;i++)
            mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
}
int ask(int l,int r)
{
    int t=log2(r-l+1);
    return min(mi[l][t],mi[r-(1<<t)+1][t]);
}
bool cmp(int a,int b) {return rk[a]<rk[b];}
int calc(int x[],int len)
{
    sort(x+1,x+1+len,cmp);
    for(int i=2;i<=len;i++)
        if(x[i]==x[i-1])
            val[i]=n-x[i]+1;
        else 
            val[i]=ask(rk[x[i-1]]+1,rk[x[i]]);
    sum=ans=top=0;
    for(int i=1;i<=len;i++)
    {
        while(val[sta[top]]>val[i]) top--;
        l[i]=i-sta[top];
        sta[++top]=i;
    }
    val[len+1]=-1,sta[++top]=len+1;
    for(int i=len;i>=1;i--)
    {
        while(val[sta[top]]>=val[i]) top--;
        r[i]=sta[top]-i;
        sta[++top]=i;
    }
    for(int i=1;i<=len;i++)
        ans+=l[i]*r[i]*val[i];
    return ans;
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n),read(m);
    cin>>s+1;
    init(s,n);
    RMQ();
    for(int i=1,len1,len2;i<=m;i++)
    {
        read(len1),read(len2);
        for(int j=1;j<=len1;j++)
            read(a[j]),
            c[j]=a[j];
        for(int j=1;j<=len2;j++)
            read(b[j]),
            c[len1+j]=b[j];
        cout<<calc(c,len1+len2)-calc(a,len1)-calc(b,len2)<<endl;
    }
}

相似子串

我們發現所有子串的左端點都在字尾陣列中出現過,問題是考慮右端點。

發現例如字尾 abcd ,可以將其分為 aababcabcd 四個子串,若其與上一個字尾的 \(LCP\)ab ,則新的子串就剩下 abcabcd

由此可得每個字尾對子串個數的貢獻為 \(len_i-height_i\) ,如此得來所有的子串已經是排好序的。

不妨使用字首和維護,利用二分查詢出第 \(i\) 個子串處於哪一個字尾中,定義為 \(pos_i\)

對於每次查詢 \(x,y\) ,處理出 \(pos_x,pos_y\) ,那麼 \(len_x=height_{pos_x}+x-sum_{pos_x-1}\)\(leny\) 同理,那麼很容易求出子串 \(x,y\) 的公共字首 \(=\min(\min(len_x,len_y),LCP(sa_{pos_x},sa_{pos_y}))\) ,當然若 \(pos_x=pos_y\) ,其只能 \(=\min(len_x,len_y)\) ,可以用 \(ST\) 表維護 \(LCP\)

問題來到如何處理公共字尾,不難想到建一個反串類似的維護,而問題是子串 \(x,y\) 在反串中的 \(pos\) 是多少。

定義 \(pos'_i\) 表示子串 \(i\) 在反串中處於哪一個字尾中,有 \(pos'_x=rk_{n-(sa_{pos_x}+len_x-1)+1}\)\(pos'_y\) 同理,即該子串右端點的 \(rk\) ,再類似上面維護即可。

雖然這樣求 \(pos'\) 不一定絕對準確,但是他能夠求出這樣的 \(pos'\) 說明他在 \(pos'\) 中一定是合法的,所以這樣去求也是沒有任何問題的,樣例 ``ababa 就是個很好的例子,aba``` 的 \(pos'\) 應該為 \(2\) ,但求出來的 \(pos'=3\) ,然而對答案沒有影響,可以手動模擬理解一下。

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int n,m,ans,sa[N][2],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N][2],sum[N][2],ls[N],mi[2][N][30];
char s[N],t[N];
void clean()
{
    memset(rk,0,sizeof(rk));
    // memset(sa,0,sizeof(sa));
    memset(id,0,sizeof(id));
    memset(oldrk,0,sizeof(rk));
    memset(cnt,0,sizeof(cnt));
    memset(key,0,sizeof(key));
    // memset(height,0,sizeof(height));
    memset(ls,0,sizeof(ls));
}
void count_sort(int n,int m,bool d)
{
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        cnt[key[i]]++;
    for(int i=1;i<=m;i++)   
        cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)
        sa[cnt[key[i]]][d]=id[i],
        cnt[key[i]]--;
}
void init(char s[],int n,bool d)
{
    clean();
    int m=127,tot=0,num=0;
    for(int i=1;i<=n;i++)
        rk[i]=(int)s[i],
        id[i]=i,
        key[i]=rk[id[i]];
    count_sort(n,m,d);
    for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
    {
        num=0;
        for(int i=n;i>=n-w+1;i--)
            num++,
            id[num]=i;
        for(int i=1;i<=n;i++)
            if(sa[i][d]>w)
                num++,
                id[num]=sa[i][d]-w;
        for(int i=1;i<=n;i++)
            key[i]=rk[id[i]];
        count_sort(n,m,d);
        for(int i=1;i<=n;i++)
            oldrk[i]=rk[i];
        tot=0;
        for(int i=1;i<=n;i++)
            tot+=(oldrk[sa[i][d]]!=oldrk[sa[i-1][d]]||oldrk[sa[i][d]+w]!=oldrk[sa[i-1][d]+w]),
            rk[sa[i][d]]=tot;
    }
    for(int i=1,k=0;i<=n;i++)
    {
        if(rk[i]==0) continue;
        if(k) k--;
        while(s[i+k]==s[sa[rk[i]-1][d]+k]) k++;
        height[rk[i]][d]=k;
    }
    for(int i=1;i<=n;i++)
        ls[i]=n-sa[i][d]+1-height[i][d],
        sum[i][d]=sum[i-1][d]+ls[i];
}
void RMQ(bool d)
{
    memset(mi[d],0x3f,sizeof(mi[d]));
    for(int i=1;i<=n;i++)
        mi[d][i][0]=height[i][d];
    for(int j=1;j<=log2(n);j++)
        for(int i=1;i<=n-(1<<j)+1;i++)
            mi[d][i][j]=min(mi[d][i][j-1],mi[d][i+(1<<(j-1))][j-1]);
}
int LCP(int l,int r,bool d)
{
    l++;
    int t=log2(r-l+1);
    return min(mi[d][l][t],mi[d][r-(1<<t)+1][t]);
}
int ask(int x,int y,bool d)
{
    int l=1,r=n,mid,pos_x,pos_y;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(sum[mid][0]<x) l=mid+1;
        if(sum[mid][0]>x) r=mid-1,pos_x=mid;
        if(sum[mid][0]==x) 
        {
            pos_x=mid;
            break;
        }
    }
    l=1,r=n;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(sum[mid][0]<y) l=mid+1;
        if(sum[mid][0]>y) r=mid-1,pos_y=mid;
        if(sum[mid][0]==y) 
        {
            pos_y=mid;
            break;
        }
    }
    int len_x=height[pos_x][0]+x-sum[pos_x-1][0],len_y=height[pos_y][0]+y-sum[pos_y-1][0];
    if(d==1) 
        pos_x=rk[n-(sa[pos_x][0]+len_x-1)+1],
        pos_y=rk[n-(sa[pos_y][0]+len_y-1)+1];
    if(pos_x>pos_y) swap(pos_x,pos_y);
    return pos_x==pos_y?min(len_x,len_y):min(min(len_x,len_y),LCP(pos_x,pos_y,d));
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n),read(m);
    cin>>s+1;
    for(int i=1;i<=n;i++)
        t[n-i+1]=s[i];
    init(s,n,0),init(t,n,1);
    RMQ(0),RMQ(1);
    for(int i=1,x,y;i<=m;i++)
    {
        read(x),read(y);
        if(x>sum[n][0]||y>sum[n][0]) puts("-1");
        else cout<<ask(x,y,0)*ask(x,y,0)+ask(x,y,1)*ask(x,y,1)<<endl;
    }
}

優秀的拆分

列舉連續串的長度 \(|s|\) ,按照 \(|s|\) 對整個串進行分塊,對相鄰兩個塊進行 \(LCP,LCS\) 查詢。

正反存兩個串,處理出 \(SA\)

定義 \(pre_i\) 表示以 \(i\) 結尾的 \(LCP\) 長度,\(suf_i\) 表示以 \(i\) 開頭的 \(LCS\) 長度。

那麼答案就等於 \(\sum\limits_{i=1}^{n-1}pre_{i+1}\times suf_i\)

列舉 \(len\) 作為分塊的長度,於是有 \(l=i,r=i+len\) 兩個關鍵點,若該兩個關鍵點的 \(LCP+LCS\geq len\) ,則更新答案。

因為是區間修改,考慮使用差分維護。

使用結構體封裝 \(SA\) 會比較方便。

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=3e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int T,n,pre[N],suf[N],ans;
struct aa
{
    char s[N];
    int rk[N],sa[N],id[N],key[N],oldrk[N],cnt[N],height[N],mi[N][30];
    void clean()
    {
        memset(rk,0,sizeof(rk));
        memset(sa,0,sizeof(sa));
        memset(id,0,sizeof(id));
        memset(oldrk,0,sizeof(rk));
        memset(cnt,0,sizeof(cnt));
        memset(key,0,sizeof(key));
        memset(height,0,sizeof(height));
    }
    void count_sort(int n,int m)
    {
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;i++)
            cnt[key[i]]++;
        for(int i=1;i<=m;i++)   
            cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;i--)
            sa[cnt[key[i]]]=id[i],
            cnt[key[i]]--;
    }
    void init(char s[],int n)
    {
        int m=127,tot=0,num=0;
        for(int i=1;i<=n;i++)
            rk[i]=(int)s[i],
            id[i]=i,
            key[i]=rk[id[i]];
        count_sort(n,m);
        for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
        {
            num=0;
            for(int i=n;i>=n-w+1;i--)
                num++,
                id[num]=i;
            for(int i=1;i<=n;i++)
                if(sa[i]>w)
                    num++,
                    id[num]=sa[i]-w;
            for(int i=1;i<=n;i++)
                key[i]=rk[id[i]];
            count_sort(n,m);
            for(int i=1;i<=n;i++)
                oldrk[i]=rk[i];
            tot=0;
            for(int i=1;i<=n;i++)
                tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
                rk[sa[i]]=tot;
        }
        for(int i=1,k=0;i<=n;i++)
        {
            if(rk[i]==0) continue;
            if(k) k--;
            while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
            height[rk[i]]=k;
        }
    }
    void ST(int n)
    {
        memset(mi,0x3f,sizeof(mi));
        for(int i=1;i<=n;i++)
            mi[i][0]=height[i];
        for(int j=1;j<=log2(n);j++)
            for(int i=1;i<=n-(1<<j)+1;i++)
                mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
    }
    int lcp(int l,int r)
    {
        l=rk[l],r=rk[r];
        if(l>r) swap(l,r);
        l++;
        int t=log2(r-l+1);
        return min(mi[l][t],mi[r-(1<<t)+1][t]);
    }
}a,b;
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(T);
    while(T--)
    {
        cin>>a.s+1;
        n=strlen(a.s+1);
        for(int i=1;i<=n;i++)
            b.s[i]=a.s[n-i+1];
        a.clean(),b.clean();
        a.init(a.s,n),b.init(b.s,n);
        a.ST(n),b.ST(n);
        for(int i=1;i<=n;i++)
            pre[i]=suf[i]=0;
        for(int len=1;len<=n/2;len++)
            for(int i=1;i<=n;i+=len)
            {
                int l1=i,r1=i+len,l2=n-(r1-1)+1,r2=n-(l1-1)+1;
                int lcp=min(a.lcp(l1,r1),len),lcs=min(b.lcp(l2,r2),len-1);
                if(lcp+lcs>=len)
                    pre[l1-lcs]++,
                    pre[l1+lcp-len+1]--,
                    suf[r1-lcs+len-1]++,
                    suf[r1+lcp]--;
            }
        for(int i=1;i<=n;i++)
            pre[i]+=pre[i-1],suf[i]+=suf[i-1];
        ans=0;
        for(int i=1;i<n;i++)
            ans+=pre[i+1]*suf[i];
        write(ans),puts("");
    }
}

結合單調棧

因為 \(LCP(sa_l,sa_r)=\min\limits_{i=l+1}^r\{height_i\}\) ,所以很多時候為了最佳化複雜度去計算 \(height_i\) 的貢獻時需要結合單調棧。

通常思路有兩種,本人主要採取類似於 廣告印刷 這道題,處理出每個 \(height_i\) 能管轄到的 \(l,r\) ,即區間 \([l\sim r]\) 中的最小值為 \(height_i\) ,那麼通常此時 \(height_i\) 能做的貢獻就是 \((i-l+1)\times (r-i+1)\times height_i\) ,依據乘法原理。

差異

結合單調棧板子題。

對於 \(\sum\limits_{i\leq i<j\leq n}len(T_i)+len(T_j)-2\times LCP(T_i,T_j)\) 這個式子,將其分為兩部分計算。

不難計算出 \(\sum\limits_{1\leq i<j\leq n}len(T_i)+len(T_j)=\dfrac{n\times(n-1)\times (n+1)}{2}\) 。首先有 \(n\) 個字串,那麼會產生 \(\dfrac{n\times (n-1)}{2}\) 對組合,我們知道每個字尾的長度平均值 \(=\dfrac{\sum\limits_{i=1}^nlen_{T_i}}{n}=\dfrac{n+1}{2}\) ,那麼兩個字尾長度加一起就是 \(n+1\) ,故答案為 \(\dfrac{n\times(n-1)\times (n+1)}{2}\)

問題主要是如何求 \(\sum\limits_{1\leq i<j\leq n}LCP(T_i,T_j)\)

\(\begin{aligned} \sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}LCP(s_{i \sim |s|,j \sim |s|}) &=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}LCP(s_{sa_{i} \sim |s|,sa_{j} \sim |s|}) \\&=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}\min\limits_{k=i+1}^{j} \{ height_{k} \} \\ &=\sum\limits_{i=2}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \} \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \}-\sum\limits_{i=1}^{n}\min\limits_{j=1}^{i} \{ height_{j} \} \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \}-\sum\limits_{i=1}^{n}0 \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \} \end{aligned}\)

根據 \(LCP(sa_l,sa_r)=\min\limits_{i=l+1}^r\{height_i\}\) ,所以很多時候為了最佳化複雜度去計算 \(height_i\) ,考慮計算每個 \(height_i\) 的貢獻,於是用到單調棧。

透過單調棧,處理出 \(\max\limits_{0\leq l<i\&height_l<height_i}\{l\}\) 以及 \(\min\limits_{i<r\leq n+1\&height_r<height_i}\{r\}\) ,那麼 \(height_i\) 的貢獻就是 \((i-l+1)\times(r-i+1)\times height_i\) ,不妨設 \(l_i=i-l+1,r_i=r-i+1\) ,那麼其貢獻就是 \(l_i\times r_i\times height_i\)

那麼最後答案就是 \(\dfrac{n\times(n-1)\times (n+1)}{2}-2\times \sum\limits_{i=1}^nl_i\times r_i\times height_i\)

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=5e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int n,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],ans,l[N],r[N];
char s[N];
int sta[N],top;
void count_sort(int n,int m)
{
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        cnt[key[i]]++;
    for(int i=1;i<=m;i++)   
        cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)
        sa[cnt[key[i]]]=id[i],
        cnt[key[i]]--;
}
void init()
{
    int m=127,tot=0,num=0;
    for(int i=1;i<=n;i++)
        rk[i]=(int)s[i],
        id[i]=i,
        key[i]=rk[id[i]];
    count_sort(n,m);
    for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
    {
        num=0;
        for(int i=n;i>=n-w+1;i--)
            num++,
            id[num]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>w)
                num++,
                id[num]=sa[i]-w;
        for(int i=1;i<=n;i++)
            key[i]=rk[id[i]];
        count_sort(n,m);
        for(int i=1;i<=n;i++)
            oldrk[i]=rk[i];
        tot=0;
        for(int i=1;i<=n;i++)
            tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
            rk[sa[i]]=tot;
    }
    for(int i=1,k=0;i<=n;i++)
    {
        if(rk[i]==0) continue;
        if(k) k--;
        while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
        height[rk[i]]=k;
    }
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    cin>>s+1;
    n=strlen(s+1);
    init();
    for(int i=1;i<=n;i++)
    {
        while(height[sta[top]]>height[i]) top--;
        l[i]=i-sta[top];
        sta[++top]=i;
    }
    sta[++top]=n+1,height[n+1]=-1;
    for(int i=n;i>=1;i--)
    {
        while(height[sta[top]]>=height[i]) top--;
        r[i]=sta[top]-i;
        ans-=2ll*l[i]*r[i]*height[i];
        sta[++top]=i;
    }
    ans+=1ll*n*(n-1)*(n+1)/2;
    cout<<ans;
}

找相同字元

設兩個串為 \(t_1,t_2\) ,那麼將兩個串拼起來成為 \(s\) ,中間用分隔符 # 隔開,定義 \(ans_x\) 表示與差異所求一樣的東西,那麼答案就是 \(ans_s-ans_{t_1}-ans_{t_2}\)

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=4e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int n,m2,m1,ans,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],l[N],r[N];
char s[N],t2[N],t1[N];
int sta[N],top; 
void count_sort(int n,int m)
{
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        cnt[key[i]]++;
    for(int i=1;i<=m;i++)   
        cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)
        sa[cnt[key[i]]]=id[i],
        cnt[key[i]]--;
}
void init(char s[],int n)
{
    memset(rk,0,sizeof(rk));
    memset(sa,0,sizeof(sa));
    memset(id,0,sizeof(id));
    memset(oldrk,0,sizeof(rk));
    memset(cnt,0,sizeof(cnt));
    memset(key,0,sizeof(key));
    memset(height,0,sizeof(height));
    memset(l,0,sizeof(l));
    memset(r,0,sizeof(r));
    int m=127,tot=0,num=0;
    for(int i=1;i<=n;i++)
        rk[i]=(int)s[i],
        id[i]=i,
        key[i]=rk[id[i]];
    count_sort(n,m);
    for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
    {
        num=0;
        for(int i=n;i>=n-w+1;i--)
            num++,
            id[num]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>w)
                num++,
                id[num]=sa[i]-w;
        for(int i=1;i<=n;i++)
            key[i]=rk[id[i]];
        count_sort(n,m);
        for(int i=1;i<=n;i++)
            oldrk[i]=rk[i];
        tot=0;
        for(int i=1;i<=n;i++)
            tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
            rk[sa[i]]=tot;
    }
    for(int i=1,k=0;i<=n;i++)
    {
        if(rk[i]==0) continue;
        if(k) k--;
        while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
        height[rk[i]]=k;
    }
}
int calc(char s[],int len)
{
    init(s,len);
    ans=top=0;
    for(int i=1;i<=len;i++)
    {
        while(height[sta[top]]>height[i]) top--;
        l[i]=i-sta[top];
        sta[++top]=i;
    }
    int x=height[len+1];
    sta[++top]=len+1,height[len+1]=-1;
    for(int i=len;i>=1;i--)
    {
        while(height[sta[top]]>=height[i]) top--;
        r[i]=sta[top]-i;
        ans+=l[i]*r[i]*height[i];
        sta[++top]=i;
    }
    height[len+1]=x;
    return ans;
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    cin>>t1+1>>t2+1;
    m1=strlen(t1+1),m2=strlen(t2+1);
    for(int i=1;i<=m1;i++)
        s[i]=t1[i];
    s[m1+1]='#';
    for(int i=1;i<=m2;i++)
        s[m1+1+i]=t2[i];
    n=m1+m2+1;
    cout<<calc(s,n)-calc(t2,m2)-calc(t1,m1);
}

Yet Another LCP Problem

上面已有。

多個串拼合

大多數找不同字串的公共串的問題都需要將多個字串拼起來處理。

字元加密

\(s\) 複製一遍變成 \(ss\) ,就轉化為字尾排序問題。

\(sa_i\leq \dfrac{n}{2}\) ,就輸出 \(s_{sa_i+\frac{n}{2}-1}\)

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int n,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N];
char s[N];
void count_sort(int n,int m)
{
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        cnt[key[i]]++;
    for(int i=1;i<=m;i++)   
        cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)
        sa[cnt[key[i]]]=id[i],
        cnt[key[i]]--;
}
void init()
{
    int m=127,tot=0,num=0;
    for(int i=1;i<=n;i++)
        rk[i]=(int)s[i],
        id[i]=i,
        key[i]=rk[id[i]];
    count_sort(n,m);
    for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
    {
        num=0;
        for(int i=n;i>=n-w+1;i--)
            num++,
            id[num]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>w)
                num++,
                id[num]=sa[i]-w;
        for(int i=1;i<=n;i++)
            key[i]=rk[id[i]];
        count_sort(n,m);
        for(int i=1;i<=n;i++)
            oldrk[i]=rk[i];
        tot=0;
        for(int i=1;i<=n;i++)
            tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
            rk[sa[i]]=tot;
    }
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    cin>>s+1;
    n=strlen(s+1);
    for(int i=1;i<=n;i++)   
        s[i+n]=s[i];
    n*=2;
    init();
    for(int i=1;i<=n;i++)
        if(sa[i]<=n/2)
            cout<<s[sa[i]+n/2-1];
}

公共串

\(t_1\sim t_n\) 拼起來得到 \(s=t_1t'_1t_2t'_2…t_nt'_n\) ,其中 \(t'_i\) 表示分隔符,每個分隔符不相同。

那麼對於每個區間 \([l,r]\) 中對於任意一個 \(t_i\) 均有一個字尾的排名在該區間中,則答案為 \(\max\limits_{1\leq l<r\leq |s|}\{\min\limits_{i=l+1}^r\{height_i\}\}\)

使用雙指標加單調佇列維護即可。

接下來詳細解釋怎麼統計是否每個 \(t_i\) 均有字尾的排名存在於區間 \(|l,r|\) 裡。

定義兩個函式 \(add,del\) ,分別處理加入一個元素與去除一個元素的情況。

先處理出每個 \(t_i\) 對應的 \(l_i,r_i\) ,即左右斷點,在此之內的每個元素使 \(c_{rk_j}=i,l_i\leq j\leq r_i\)

定義 \(vis_i\) 記錄 \(i\) 出現的次數,當插入一個指標 \(i\) 時,先判斷是否為分隔符,若不是則 \(vis_{c_i}++\)\(sum+=[vis_{c_i}=1]\) ,同理 \(del\) 函式對應 \(vis_{c_i}--\)\(sum-=[vis_{c_i}=0]\)

那麼當 \(sum=n\) 時,即 \(l,r\) 滿足條件。

同時因為 \(LCP(sa_l,sa_r)=\min\limits_{i=l+1}^r\{height_i\}\) ,所以我們希望這個區間儘可能小,那麼列舉 \(r\) ,同時當 \(l,r\) 滿足條件的前提下另 \(l\) 儘可能大,從而使處理答案的複雜度控制在 \(O(n)\)

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int n,m,ans,sum,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],l[N],r[N],c[N],v[N],L[N],R[N];
char s[N],t[N];
deque<int>q;
void clean()
{
    memset(rk,0,sizeof(rk));
    memset(sa,0,sizeof(sa));
    memset(id,0,sizeof(id));
    memset(oldrk,0,sizeof(rk));
    memset(cnt,0,sizeof(cnt));
    memset(key,0,sizeof(key));
    memset(height,0,sizeof(height));
    memset(l,0,sizeof(l));
    memset(r,0,sizeof(r));
}
void count_sort(int n,int m)
{
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        cnt[key[i]]++;
    for(int i=1;i<=m;i++)   
        cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)
        sa[cnt[key[i]]]=id[i],
        cnt[key[i]]--;
}
void init(char s[],int n)
{
    int m=127,tot=0,num=0;
    for(int i=1;i<=n;i++)
        rk[i]=(int)s[i],
        id[i]=i,
        key[i]=rk[id[i]];
    count_sort(n,m);
    for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
    {
        num=0;
        for(int i=n;i>=n-w+1;i--)
            num++,
            id[num]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>w)
                num++,
                id[num]=sa[i]-w;
        for(int i=1;i<=n;i++)
            key[i]=rk[id[i]];
        count_sort(n,m);
        for(int i=1;i<=n;i++)
            oldrk[i]=rk[i];
        tot=0;
        for(int i=1;i<=n;i++)
            tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
            rk[sa[i]]=tot;
    }
    for(int i=1,k=0;i<=n;i++)
    {
        if(rk[i]==0) continue;
        if(k) k--;
        while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
        height[rk[i]]=k;
    }
}
void add(int x,int &sum)
{
    if(c[x]==0) return ;
    v[c[x]]++;
    sum+=(v[c[x]]==1);
}
void del(int x,int &sum)
{
    if(c[x]==0) return ;
    v[c[x]]--;
    sum-=(v[c[x]]==0);
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(m);
    for(int i=1;i<=m;i++)
    {
        cin>>t+1;
        int len=strlen(t+1);
        L[i]=n+1;
        for(int j=1;j<=len;j++)
            s[++n]=t[j];
        R[i]=n;
        s[++n]=i+'#';
    }
    init(s,n);
    for(int i=1;i<=m;i++)
        for(int j=L[i];j<=R[i];j++)
            c[rk[j]]=i;
    add(1,sum);
    for(int l=1,r=2;r<=n;r++)
    {
        while(!q.empty()&&height[q.front()]>=height[r])
            q.pop_front();
        q.push_front(r);
        add(r,sum);
        if(sum>=m)
        {
            while(sum>=m&&l<=r-1)
                del(l,sum),
                l++;
            l--;
            add(l,sum);
        }
        while(!q.empty()&&q.back()<=l)
            q.pop_back();
        if(sum==m)
            ans=max(ans,height[q.back()]);
    }
    cout<<ans;
}

Sandy 的卡片

查分後變為 公共串 ,因為差分後長度會 \(-1\) ,所以最後答案要 \(+1\)

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int n,m,ans,sum,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],l[N],r[N],c[N],v[N],L[N],R[N];
int s[N],t[N];
deque<int>q;
void clean()
{
    memset(rk,0,sizeof(rk));
    memset(sa,0,sizeof(sa));
    memset(id,0,sizeof(id));
    memset(oldrk,0,sizeof(rk));
    memset(cnt,0,sizeof(cnt));
    memset(key,0,sizeof(key));
    memset(height,0,sizeof(height));
    memset(l,0,sizeof(l));
    memset(r,0,sizeof(r));
}
void count_sort(int n,int m)
{
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        cnt[key[i]]++;
    for(int i=1;i<=m;i++)   
        cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)
        sa[cnt[key[i]]]=id[i],
        cnt[key[i]]--;
}
void init(int s[],int n)
{
    int m=5000,tot=0,num=0;
    for(int i=1;i<=n;i++)
        rk[i]=(int)s[i],
        id[i]=i,
        key[i]=rk[id[i]];
    count_sort(n,m);
    for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
    {
        num=0;
        for(int i=n;i>=n-w+1;i--)
            num++,
            id[num]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>w)
                num++,
                id[num]=sa[i]-w;
        for(int i=1;i<=n;i++)
            key[i]=rk[id[i]];
        count_sort(n,m);
        for(int i=1;i<=n;i++)
            oldrk[i]=rk[i];
        tot=0;
        for(int i=1;i<=n;i++)
            tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
            rk[sa[i]]=tot;
    }
    for(int i=1,k=0;i<=n;i++)
    {
        if(rk[i]==0) continue;
        if(k) k--;
        while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
        height[rk[i]]=k;
    }
}
void add(int x,int &sum)
{
    if(c[x]==0) return ;
    v[c[x]]++;
    sum+=(v[c[x]]==1);
}
void del(int x,int &sum)
{
    if(c[x]==0) return ;
    v[c[x]]--;
    sum-=(v[c[x]]==0);
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(m);
    for(int i=1,len;i<=m;i++)
    {
        cin>>len;
        L[i]=n+1;
        for(int j=1;j<=len;j++)
        {
            read(t[j]);
            if(j>=2)
                s[++n]=2000+t[j]-t[j-1];
        }
        R[i]=n;
        s[++n]=i+2000;
    }
    init(s,n);
    for(int i=1;i<=m;i++)
        for(int j=L[i];j<=R[i];j++)
            c[rk[j]]=i;
    add(1,sum);
    for(int l=1,r=2;r<=n;r++)
    {
        while(!q.empty()&&height[q.front()]>=height[r])
            q.pop_front();
        q.push_front(r);
        add(r,sum);
        if(sum>=m)
        {
            while(sum>=m&&l<=r-1)
                del(l,sum),
                l++;
            l--;
            add(l,sum);
        }
        while(!q.empty()&&q.back()<=l)
            q.pop_back();
        if(sum==m)
            ans=max(ans,height[q.back()]);
    }
    cout<<ans+1;
}

找相同字元

上面已有,由於涉及將兩個串拼起來的思路,所以也放在了這裡。

Yet Another LCP Problem

上面已有,同樣的涉及將兩個串拼起來。

結合莫隊

喵星球上的點名

將所有姓名拼起來,中間用不同分隔符斷開,處理出 \(SA\)

對於每一個查詢串,發現其對應原串可以匹配位置可以用區間 \([l,r]\) 表示,可以用每個位置都二分處理出左右端點。

對於每個字尾處理出其對應那個人,用 \(a_i\) 表示。

於是問題一轉變成區間數顏色問題,最基本的莫隊維護即可,可見 HH 的項鍊

至於第二問,不妨在新增和刪除操作時傳入引數 \(i\) ,即處理到哪個問題。

當新新增入該串時,另其 \(ans+=tot-i+1\) ,刪除該串時,則另其 \(ans-tot-i+1\)\(tot\) 表示合法的查詢個數,由此問題二解決。

點選檢視程式碼
#include<bits/stdc++.h>
// #define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=5e6+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,m,t,tot,tot_m,s[N],id[N],rk[N],sa[N],cnt[N],oldrk[N],key[N],height[N],a[N],pos[N],anss[N],ans,ans_tot[N];
struct aa
{
    int l,r,id;
}e[N];
bool cmp(aa a,aa b) {return pos[a.l]==pos[b.l]?a.r<b.r:a.l<b.l;}
void clean()
{
    memset(rk,0,sizeof(rk));
    memset(sa,0,sizeof(sa));
    memset(id,0,sizeof(id));
    memset(oldrk,0,sizeof(rk));
    memset(cnt,0,sizeof(cnt));
    memset(key,0,sizeof(key));
    memset(height,0,sizeof(height));
}
void count_sort(int n,int m)
{
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        cnt[key[i]]++;
    for(int i=1;i<=m;i++)   
        cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)
        sa[cnt[key[i]]]=id[i],
        cnt[key[i]]--;
}
void init(int s[],int n)
{
    int m=300000,tot=0,num=0;
    for(int i=1;i<=n;i++)
        rk[i]=(int)s[i],
        id[i]=i,
        key[i]=rk[id[i]];
    count_sort(n,m);
    for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
    {
        num=0;
        for(int i=n;i>=n-w+1;i--)
            num++,
            id[num]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>w)
                num++,
                id[num]=sa[i]-w;
        for(int i=1;i<=n;i++)
            key[i]=rk[id[i]];
        count_sort(n,m);
        for(int i=1;i<=n;i++)
            oldrk[i]=rk[i];
        tot=0;
        for(int i=1;i<=n;i++)
            tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
            rk[sa[i]]=tot;
    }
    for(int i=1,k=0;i<=n;i++)
    {
        if(rk[i]==0) continue;
        if(k) k--;
        while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
        height[rk[i]]=k;
    }
}
void add(int &ans,int x,int i)
{
    cnt[a[x]]++;
    if(cnt[a[x]]==1) 
        ans++,
        ans_tot[a[x]]+=tot_m-i+1;
}
void del(int &ans,int x,int i)
{
    cnt[a[x]]--;
    if(cnt[a[x]]==0)
        ans--,
        ans_tot[a[x]]-=tot_m-i+1;
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n),read(m);
    t=sqrt(n);
    int tmp=10000;
    for(int i=1,len;i<=n;i++)
    {                    
        read(len);
        for(int j=1;j<=len;j++)
            read(s[++tot]),
            a[tot]=i;
        s[++tot]=++tmp;
        read(len);
        for(int j=1;j<=len;j++)
            read(s[++tot]),
            a[tot]=i;
        s[++tot]=++tmp;                           
        pos[i]=(i-1)/t+1;
    }
    init(s,tot); 
    for(int i=1,len;i<=m;i++)
    {
        read(len);
        int ll=1,rr=tot;
        for(int j=1,x;j<=len;j++)
        {
            read(x);
            int l=ll,r=rr,mid;
            while(l<=r)
            {
                mid=(l+r)>>1;
                if(s[sa[mid]+j-1]<x) l=mid+1;
                else r=mid-1;
            }
            int ls=l;
            l=ll,r=rr;
            while(l<=r)
            {
                int mid=(l+r)>>1;
                if(s[sa[mid]+j-1]<=x) l=mid+1;
                else r=mid-1;
            }
            ll=ls,rr=r;
        }
        if(ll<=rr) e[++tot_m]={ll,rr,i};
    }
    sort(e+1,e+1+tot_m,cmp);
    int l=1,r=0;
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=tot_m;i++)
    {
        while(l>e[i].l) add(ans,sa[--l],i);
        while(l<e[i].l) del(ans,sa[l++],i);
        while(r<e[i].r) add(ans,sa[++r],i);
        while(r>e[i].r) del(ans,sa[r--],i);
        anss[e[i].id]=ans;
    }
    for(int i=1;i<=m;i++)
        write(anss[i]),puts("");
    for(int i=1;i<=n;i++)
        write(ans_tot[i]),cout<<' ';
}

結合單調佇列

因為 \(LCP(sa_l,sa_r)=\min\limits_{i=l+1}^r\{height_i\}\) ,所以類似於求帶限制的最長公共子串問題時常結合單調佇列,常見的形式為求 \(\max\limits_{1\leq l<r\leq |s|}\{\min\limits_{i=l+1}^r\{height_i\}\}\) ,且同時常存在隊內長度的限制。

下面的例題前面都已經涉及,就只沾題目了。

Milk Patterns G

公共串

Sandy 的卡片

結合並查集

品酒大會

即求字尾樹組劃分成連續若干個 \(height\) 長度 \(\geq r\) 的段並統計每一段的答案。

我們發現若其為 “\(r\) 相似” 的,他還會對 “\(0\sim r\) 相似” 做出貢獻,當給定的 \(r→r-1\) 時,新的區間為 區間 \(r-1\) 與區間 \(r\) 合併所得,故此維護一個並查集,每次合併兩個相鄰的區間,並維護統計資訊即可。

不妨將 \(r\) 按照 \(height\) 遞減排序,那麼每次合併的就是 \(sa_{r_i}\)\(sa_{r_i-1}\) ,即統計 \(height_{r_i}\) 的貢獻。

定義 \(sum_r\) 表示 \(r\) 相似的方案數,\(ans_r\) 表示其美味度的最大值。

對於兩個區間 \(x,y\) 合併,維護 \(max_x,min_x,size_x\)\(y\) 同理,分別表示其 \(a_i\) 的最大值、最小值,以及該區間元素的個數。

不妨定義 \(z\) 表示 \(height_{r_i}\) ,那麼有:

  • \(ans_z=\max\{ans_z,max_x\times max_y,min_x\times min_y\}\) ,防止負負得正的情況。

  • \(sum_z+=size_x\times size_y\) ,可以理解一下。

  • 之後就是基本的合併操作:

    • \(max_x=\max(max_x,max_y)\)

    • \(min_x=\min(min_x,mix_y)\)

    • \(size_x+=size_y\)

    • \(f_y=x\)

    當然最開始的 \(x=find(x),y=find(y)\) 是必須的。

最後統計答案即可。

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=3e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int n,m,ans[N],sum[N],sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],a[N],f[N],r[N],mi[N],mx[N],siz[N],maxx[N];
char s[N];
void clean()
{
    memset(rk,0,sizeof(rk));
    memset(sa,0,sizeof(sa));
    memset(id,0,sizeof(id));
    memset(oldrk,0,sizeof(rk));
    memset(cnt,0,sizeof(cnt));
    memset(key,0,sizeof(key));
    memset(height,0,sizeof(height));
}
void count_sort(int n,int m)
{
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        cnt[key[i]]++;
    for(int i=1;i<=m;i++)   
        cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)
        sa[cnt[key[i]]]=id[i],
        cnt[key[i]]--;
}
void init(char s[],int n)
{
    int m=127,tot=0,num=0;
    for(int i=1;i<=n;i++)
        rk[i]=(int)s[i],
        id[i]=i,
        key[i]=rk[id[i]];
    count_sort(n,m);
    for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
    {
        num=0;
        for(int i=n;i>=n-w+1;i--)
            num++,
            id[num]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>w)
                num++,
                id[num]=sa[i]-w;
        for(int i=1;i<=n;i++)
            key[i]=rk[id[i]];
        count_sort(n,m);
        for(int i=1;i<=n;i++)
            oldrk[i]=rk[i];
        tot=0;
        for(int i=1;i<=n;i++)
            tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
            rk[sa[i]]=tot;
    }
    for(int i=1,k=0;i<=n;i++)
    {
        if(rk[i]==0) continue;
        if(k) k--;
        while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
        height[rk[i]]=k;
    }
}
int find(int x)
{
    return f[x]==x?x:f[x]=find(f[x]);
}
bool cmp(int a,int b) {return height[a]>height[b];}
void merge(int x,int y,int z)
{
    x=find(x),y=find(y);
    if(x==y) return ;
    maxx[x]=max({maxx[x],maxx[y],mx[x]*mx[y],mi[x]*mi[y]});
    sum[z]+=siz[x]*siz[y];
    ans[z]=max(ans[z],maxx[x]);
    mx[x]=max(mx[x],mx[y]);
    mi[x]=min(mi[x],mi[y]);
    f[y]=x;
    siz[x]+=siz[y];
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n);
    cin>>s+1;
    init(s,n);
    memset(maxx,-0x3f,sizeof(maxx));
    memset(ans,-0x3f,sizeof(ans));
    for(int i=1;i<=n;i++)
        read(a[i]),
        r[i]=f[i]=i,
        siz[i]=1,
        mx[i]=mi[i]=a[i];
    sort(r+1,r+1+n,cmp);
    for(int i=1;i<=n;i++)
        merge(sa[r[i]],sa[r[i]-1],height[r[i]]);
    for(int i=n-1;i>=0;i--)
        sum[i]+=sum[i+1],
        ans[i]=max(ans[i],ans[i+1]);
    for(int i=0;i<=n-1;i++)
        cout<<sum[i]<<' '<<(sum[i]!=0)*ans[i]<<endl;
}

相關文章