后缀数组

强颜欢笑 提交于 2019-12-01 10:19:25

luogu回文串

题意给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两个子串中有一个位置不同。

首先我们知道可以用马拉车算法算出该字符串然后再用height来求出答案

但是如果暴力的话,肯定不行,所以我们运用马拉车的一个性质

    for(int i=1;i<len;++i) {
        if(mx>i) p[i]=min(p[id*2-i],mx-i);
        else p[i]=1;
        while(ms[i+p[i]]==ms[i-p[i]]) {
            ++p[i]; 
            if(i+p[i]>mx) {
                //只有在这里突破了mx的子串才是之前没有出现过的回文子串.
                //算入分隔符#的限制的话.
                calc(i-p[i]+1,i+p[i]-1);
            }
        }
        if(p[i]+i>mx) {
            mx=i+p[i],id=i;
        }
    }   

  

实测发现,Manacher算法的此类拓展不一定提交本质不同的回文子串(不算#),因为会有#的限制,

但是结合Manacher算法对称性(关于id对称)的原理可以发现,不在此类拓展中提交(calc)的子串一定在之前提到过.

而Manacher总复杂度O(n),所以是完全能够接受的.

然后问题就换成了如何统计一个回文子串(也就是一个子串)的出现次数.

这里使用后缀数组解决.

一个子串是某一个后缀的前缀,因而,如果一个子串在K个后缀中出现过,那么该子串一定是这K个后缀Lcs的前缀.

也就有len(LCS)>=len(子串)

易知这K个后缀排序后构成连续的一段,所以可以用后缀数组解决.

每次在Height数组上二分,向前后分别拓展,rmq查询判断Lcs长度是否满足要求即可. 总复杂度O(nlogn)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const ll M=530000;

ll a[M],b[M],rk[M],sa[M],height[M],x[M],y[M],c[M],id[M],p,top,st[M];
ll n,m,vis[M],maxy=99999;
ll temp,s,len1,len2,ans;
struct nod
{
    ll height,s;
}node;
stack<nod>stk;
void get_sa()
{
    for(ll i=1;i<=n;i++) ++c[x[i]=b[i]];
    for(ll i=2;i<=m;i++) c[i]+=c[i-1];
    for(ll i=n;i>=1;--i) sa[c[x[i]]--]=i;
    for(ll k=1;k<=n;k<<=1){
        ll num=0;
        for(ll i=n-k+1;i<=n;i++) y[++num]=i;
        for(ll i=1;i<=n;i++) if(sa[i]>k) y[++num]=sa[i]-k;
        for(ll i=1;i<=m;i++) c[i]=0;
        for(ll i=1;i<=n;i++) ++c[x[i]];
        for(ll i=2;i<=m;i++) c[i]+=c[i-1];
        for(ll i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
        swap(x,y);
        x[sa[1]]=1;
        num=1;
        for(ll i=2;i<=n;i++)
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            if(num==n) break;
            m=num;
    }
}
void get_height()
{
    ll k=0,j;
    for(ll i=1;i<=n;i++) rk[sa[i]]=i;
    for(ll i=1;i<=n;i++){
        if(rk[i]==1) continue;
        if(k)k--;
        j=sa[rk[i]-1];
        while(j+k<=n&&i+k<=n&&b[i+k]==b[j+k]) k++;
        height[rk[i]]=k;
    }
}
void solve()
{
    for(ll i=1;i<=n;i++){
        s=0;
        while(stk.size()>0&&height[i]<stk.top().height){
            s+=stk.top().s;
            temp+=(height[i]-stk.top().height)*stk.top().s;
            stk.pop();
        }
        if(sa[i-1]<=len1) temp+=height[i],s++;
        if(sa[i]>len1) ans+=temp;
        node.height=height[i];node.s=s;
        stk.push(node);
    }
    while(stk.size()>0) stk.pop();
    for(ll i=1;i<=n;i++){
        s=0;
        while(stk.size()>0&&height[i]<stk.top().height){
            s+=stk.top().s;
            temp+=(height[i]-stk.top().height)*stk.top().s;
            stk.pop();
        }
        if(sa[i-1]>len1) temp+=height[i],s++;
        if(sa[i]<=len1) ans+=temp;
        node.height=height[i];node.s=s;
        stk.push(node);
    }
    printf("%lld\n",ans);
}
int main()
{
    char s1[200005],s2[200005];
    scanf("%s",s1);
    scanf("%s",s2);
    len1=strlen(s1),len2=strlen(s2);
    for(ll i=1;i<=len1;i++) b[i]=s1[i-1];
    b[len1+1]=666;
    for(ll i=1;i<=len2;i++) b[len1+1+i]=s2[i-1];
    n=len1+len2+1;
    m=777;
    get_sa();
    get_height();
    solve();
}

 luogu喵星人点名

这是一道后缀数组+莫队的题
题意,给出每个单位一个姓与名,然后询问的字符串里某单单位的性或名的子串,
则称该单位被询问了一次,第一问,每个询问问到了多少个单位,下文解释到这是个区间不同颜色,
第二问:每只单位出现次数
如果一个串是某些后缀的前缀,那么这些后缀排名肯定是连续的,所以在sa数组上连续区间这时,我们在二分出这个区间,再看看这个区间有多少个颜色,也就是多少个单位,这就用莫队回答了第一个问
对于第二问:可以进行差分
当莫队在处理第j个询问中发现一个新出现的颜色i时,num[i]+=cnt-j+1(最大可能总数);cnt是总询问数。

当莫队在处理第j个询问中发现少了一个颜色i时,num[i]−=cnt−j+1(也就是此刻没了后面的颜色询问这个单位是没有占到的)

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
inline int gi() {
    register int data = 0, w = 1;
    register char ch = 0;
    while (!isdigit(ch) && ch != '-') ch = getchar();
    if (ch == '-') w = -1, ch = getchar();
    while (isdigit(ch)) data = 10 * data + ch - '0', ch = getchar();
    return w * data;
}
const int MAX_N = 2e5 + 5;
int N, n, m, a[MAX_N], id[MAX_N], lim = 1e4 + 5;
int sa[MAX_N];
void GetSA() {
#define cmp(i, j, k) (y[i] == y[j] && y[i + k] == y[j + k])
    static int x[MAX_N], y[MAX_N], bln[MAX_N];
    int M = lim;
    for (int i = 1; i <= N; i++) bln[x[i] = a[i]]++;
    for (int i = 1; i <= M; i++) bln[i] += bln[i - 1];
    for (int i = N; i >= 1; i--) sa[bln[x[i]]--] = i;
    for (int k = 1; k <= N; k <<= 1) {
        int p = 0;
        for (int i = 0; i <= M; i++) y[i] = 0;
        for (int i = N - k + 1; i <= N; i++) y[++p] = i;
        for (int i = 1; i <= N; i++) if (sa[i] > k) y[++p] = sa[i] - k;
        for (int i = 0; i <= M; i++) bln[i] = 0;
        for (int i = 1; i <= N; i++) bln[x[y[i]]]++;
        for (int i = 1; i <= M; i++) bln[i] += bln[i - 1];
        for (int i = N; i >= 1; i--) sa[bln[x[y[i]]]--] = y[i];
        swap(x, y); x[sa[1]] = p = 1;
        for (int i = 2; i <= N; i++) x[sa[i]] = cmp(sa[i], sa[i - 1], k) ? p : ++p;
        if (p >= N) break;
        M = p;
    }
}
const int LEN = 400;
int cnt[MAX_N], bln[MAX_N];
struct Query { int l, r, id; } q[MAX_N]; int q_cnt = 0;
//inline bool operator < (const Query &a, const Query &b) {
//    if (bln[a.l] ^ bln[b.l]) return a.r < b.r;
//    else return (bln[a.l] & 1) ? a.r < b.r : a.r > b.r;
//}
int CMP(Query a,Query b)
{
    if(bln[a.l]==bln[b.l] ) return a.r<b.r;
    else return bln[a.l]<bln[b.l];
}
int A1, ans1[MAX_N], ans2[MAX_N];

void add(int x, int pos){
    cnt[id[sa[x]]]++;
    if (cnt[id[sa[x]]] == 1) ++A1, ans2[id[sa[x]]] += q_cnt - pos + 1;
}
void del(int x, int pos){
    cnt[id[sa[x]]]--;
    if (cnt[id[sa[x]]] == 0) --A1, ans2[id[sa[x]]] -= q_cnt - pos + 1;
}
int main () {
//#ifndef ONLINE_JUDGE
//    freopen("cpp.in", "r", stdin);
//#endif
    n = gi(), m = gi();
    for (int i = 1, L; i <= n; i++) {
        L = gi(); for (int j = 1; j <= L; j++) a[++N] = gi(), id[N] = i; a[++N] = ++lim;
        L = gi(); for (int j = 1; j <= L; j++) a[++N] = gi(), id[N] = i; a[++N] = ++lim;
    }
    GetSA();
    for (int i = 1; i <= m; i++) {///首先就是两个while可以求出一个二分的最大左右区间,
        int L = 1, R = N;
        for (int len = gi(), j = 1; j <= len; ++j) {
            int x = gi(), l = L, r = R;
            while (l <= r) {
                int mid = (l + r) >> 1;
                if (a[sa[mid] + j - 1] < x) l = mid + 1;
                else r = mid - 1;
            }
            int tmp = l; l = L, r = R;
            while (l <= r) {
                int mid = (l + r) >> 1;
                if (a[sa[mid] + j - 1] <= x) l = mid + 1;
                else r = mid - 1;
            }
            L = tmp, R = r;
        }
        if (L <= R) q[++q_cnt] = (Query){L, R, i};
    }
    for (int i = 1; i <= N; i++) bln[i] = (i - 1) / LEN + 1;
    sort(q+1, q+1+q_cnt,CMP);
    for (int ql = 1, qr = 0, i = 1; i <= q_cnt; i++) {
        while (ql < q[i].l) del(ql, i), ++ql;
        while (ql > q[i].l) --ql, add(ql, i);
        while (qr < q[i].r) ++qr, add(qr, i);
        while (qr > q[i].r) del(qr, i), --qr;
        ans1[q[i].id] = A1;
    }
    for (int i = 1; i <= m; i++) printf("%d\n", ans1[i]);
    for (int i = 1; i <= n; i++) printf("%d ", ans2[i]);
    return 0;
    }
}

  luogu找相同字符串

差分就完事

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const ll M=530000;

ll a[M],b[M],rk[M],sa[M],height[M],x[M],y[M],c[M],id[M],p,top,st[M];
ll n,m,vis[M],maxy=99999;
ll temp,s,len1,len2,ans;
struct nod
{
    ll height,s;
}node;
stack<nod>stk;
void get_sa()
{
    for(ll i=1;i<=n;i++) ++c[x[i]=b[i]];
    for(ll i=2;i<=m;i++) c[i]+=c[i-1];
    for(ll i=n;i>=1;--i) sa[c[x[i]]--]=i;
    for(ll k=1;k<=n;k<<=1){
        ll num=0;
        for(ll i=n-k+1;i<=n;i++) y[++num]=i;
        for(ll i=1;i<=n;i++) if(sa[i]>k) y[++num]=sa[i]-k;
        for(ll i=1;i<=m;i++) c[i]=0;
        for(ll i=1;i<=n;i++) ++c[x[i]];
        for(ll i=2;i<=m;i++) c[i]+=c[i-1];
        for(ll i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
        swap(x,y);
        x[sa[1]]=1;
        num=1;
        for(ll i=2;i<=n;i++)
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            if(num==n) break;
            m=num;
    }
}
void get_height()
{
    ll k=0,j;
    for(ll i=1;i<=n;i++) rk[sa[i]]=i;
    for(ll i=1;i<=n;i++){
        if(rk[i]==1) continue;
        if(k)k--;
        j=sa[rk[i]-1];
        while(j+k<=n&&i+k<=n&&b[i+k]==b[j+k]) k++;
        height[rk[i]]=k;
    }
}
void solve()
{
    for(ll i=1;i<=n;i++){
        s=0;
        while(stk.size()>0&&height[i]<stk.top().height){
            s+=stk.top().s;
            temp+=(height[i]-stk.top().height)*stk.top().s;
            stk.pop();
        }
        if(sa[i-1]<=len1) temp+=height[i],s++;
        if(sa[i]>len1) ans+=temp;
        node.height=height[i];node.s=s;
        stk.push(node);
    }
    while(stk.size()>0) stk.pop();
    for(ll i=1;i<=n;i++){
        s=0;
        while(stk.size()>0&&height[i]<stk.top().height){
            s+=stk.top().s;
            temp+=(height[i]-stk.top().height)*stk.top().s;
            stk.pop();
        }
        if(sa[i-1]>len1) temp+=height[i],s++;
        if(sa[i]<=len1) ans+=temp;
        node.height=height[i];node.s=s;
        stk.push(node);
    }
    printf("%lld\n",ans);
}
int main()
{
    char s1[200005],s2[200005];
    scanf("%s",s1);
    scanf("%s",s2);
    len1=strlen(s1),len2=strlen(s2);
    for(ll i=1;i<=len1;i++) b[i]=s1[i-1];
    b[len1+1]=666;
    for(ll i=1;i<=len2;i++) b[len1+1+i]=s2[i-1];
    n=len1+len2+1;
    m=777;
    get_sa();
    get_height();
    solve();
}

  

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!