后缀数组2应用(内容总结自罗DADA论文)

主函数

int main()
{
    scanf("%s",st);
    int len = strlen(st);
    for(int i = 0; i < len; i++) r[i] = st[i];
    r[len] = 1;
    for(int i = 0; i < len; i++) r[i+len+1] = st[len-1-i];
    int n = 2*len+1;
    r[n] = 0;
    da(r, sa, n+1, 128);
    Get_Height(r, sa, n);
    for(int i = 1; i <= n; i++) RMQ[i] = height[i];
    initRMQ(n);
   /**此处添加功能函数**/
}

1、最长公共前缀

height 数组:定义 height[i] = (suffix(sa[i-1]), suffix(sa[i]))最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀。

int rank[M], height[M];
void calheight(int *r, int *sa, int n)
{
    int i,j,k=0;
    for(i=1;i<=n;i++) rank[sa[i]]=i;
    for(i=0;i

2、单个字符串的相关问题

常用做法是先求后缀数组和 height 数组,然后利用 height数组进行求解。

  • 重复子串(串 R 在串 L 中至少出现两次,称 R 是 L 的重复子串。)

//可重叠 
int height[M];
void CHONG(int *H, int n)
{
      int maxx = 0;
      for (int i = 1; i <= n; i++)
           maxx = max(maxx, H[i]);
}

//不可重叠(二分答案,判断是否存在长度相同的两个子串,且不重叠)
bool check(int *sa,int n,int k)
{
    int maxx=sa[1], minn=sa[1];
    for(int i = 2; i <= n; i++)
    {
        if(height[i] < k) maxx = minn = sa[i];
        else
        {
            minn = min(minn, sa[i]);
            maxx = max(maxx, sa[i]);
            if(maxx-minn > k) return true;
        }
    }
    return false;
}
void solve(int *sa, int n)
{
    L = 1;
    R = n/2;
    while(L <= R)
    {
        mid = (L + R)/2;
        if(check(sa, n, mid)) L = mid+1;
        else R = mid-1;
    }
    if(R >= 4) printf("%d\n", R+1);
    else printf("0\n");
}

/*可重复K次(判断是否存在一个组的后缀个数不小于 k。如果有,便存在k 个相同的子串满足条件,否则不存在。)*/
bool check(int n, int k, int mid)
{
    int s = 1;
    for(int i = 1; i <= n; i++)
    {
        if(height[i] >= mid)
        {
            s++;
            if(s >= k) return true;
        }
        else s = 1;
    }
    return false;
}
void solve(int *sa, int n)
{
    int L = 1;
    int R = n/2;
    while(L <= R)
    {
        int mid = (L + R)/2;
        if(check(sa, n, mid)) L = mid+1;
        else R = mid-1;
    }
    printf("%d\n", R);
}
  • 子串的个数

给定一个字符串,判断其中存在多少子串(可以用字典树)

/*对于每一次新加进来的后缀 suffix(sa[k]), 将产生 n-sa[k]+1 个新的前缀。
但是其中有height[k]个是和前面的字符串的前缀是相同的。
所以 suffix(sa[k])将“贡献”出 n-sa[k]+1- height[k]个不同的子串。*/
LL Get_Kind(int *height, int n)
{
    ans =(LL)n * (n+1) / 2;
    for(int i = 1; i <= n; i++) 
        ans -= height[i];
    return ans;
}
  • 回文子串(也可用回文树)

//求所有后缀之间的不相同的前缀的个数
//最长回文字串
/*求一个后缀 和 一个反过来写的后缀 的最长公共前缀
    将整个字符串反过来写在原字符串后面,中间用一个特殊的字符隔开
    问题变为求这个新的字符串的某两个后缀的最长公共前缀
栗子:
字符串banana
新串 banana#ananab
后缀 na#ananab与na#ananab的公共前缀na
后缀 ana#ananab与anab的公共前缀ana
...
*/
void solve()
{
    int k, w, ans;
    for(int i = 0; i < len; i++)//穷举每一位,计算以这个字符为中心的最长回文子串
    {
        k = LCP(i, n-i);
        if(k*2 > ans) //偶对称
        {
            ans = k*2;
            w = i-k;
        }
        k = LCP(i, n-i-1);
        if(k*2-1 > ans) //奇对称
        {
            ans = k*2-1;
            w = i-k+1;
        }
    }
    st[w+ans] = 0;
    printf("%s\n",st+w);
}
  • 连续重复子串

定义::如果一个字符串 L 是由某个字符串 S 重复 R 次而得到的,则称L 是一个连续重复串。R 是这个字符串的重复次数。
步骤:
1、穷举字符串 S 的长度 k,判断其是否满足。
判断过程:
字符串 L 的长度能否被 k 整除,
suffix(1)和 suffix(k+1)的最长公共前缀是否等于 n-k

2、因为suffix(1)是固定的,预处理RMQ问题,求出height 数组中的每一个数到
height[rank[1]]之间的最小值即为最长公共前缀

int RE(int n)
{//穷举长度L,看字符 r[L*i]和 r[L*(i+1)]往前和往后各能匹配到多远
    int ans = 0;
    int k, now, step;
    for(int i = 1; i < n; i++)
    {
        for(int j = 0; j+i < n; j += i)
        {
            k = LCP(j, j+i);
            now = k / i;
            step = j - (i-k%i);
            if(step>=0 && LCP(step, step+i)>=(i-k%i)) now++;
            if(now > ans) ans = now;
        }
    }
    return ans+1;
}

3、两个字符串的相关问题

先连接这两个字符串,然后求后缀数组和height 数组,再利用 height 数组进行求解
A:aabba
B:aab
新串: aabba#aab
对新串进行SA处理

  • 公共字串(同时出现在字符串 A 和字符串 B 中的字符串L)

给定两个字符串 A 和 B,求最长公共子串,等于求‘A的后缀’和‘B的后缀’的最长公共前缀 的最大值。
将第二个字符串写在第一个字符串后面,中间用一个未出现过的字符隔开,再求这个新的字符串的后缀数组。
求height[i]的最大值,但是要注意,只有suffix(sa[i-1])和suffix(sa[i])不是同一个字符串中的两个后缀时,height[i]才是满足条件的。

void solve(int *sa, int *height, int n)
{
    for(int i = 2; i <= n; i++)
        if(height[i] > ans)
            if((jsa[i]) || (j>sa[i-1]&&j
  • 子串的个数(不懂QAQ)

计算 A 的所有后缀和 B 的所有后缀之间的最长公共前缀的长度,把最长公共前缀长度不小于 k 的部分全部加起来。
步骤:
按 height 值分组,求每组中后缀之间的最长公共前缀之和
扫描一遍,每遇到一个 B 的后缀就统计与前面的 A 的后缀能产生多少个长度不小于 k 的公共子串,这里 A 的后缀需要用一个单调的栈来高效的维护。然后对 A 也这样做一次。

//长度不小于 k 的公共子串的个数
int f[M], a[M], b[M],;
void solve(int *height, int *sa)
{
    for(int i = 2; i <= n; i++)
    {
        f[i] = sa[i] < l;
        height[i] -= k-1;
        if(height[i] < 0) height[i] = 0;
    }
    height[n+1] = 0;
    a[0] = -1;
    LL ans = 0;
    for(int t = 0; t <= 1; t++)
    {
        int c = 0;
        LL ss = 0;
        for(int i = 2; i <= n; i++)
        {
            if(f[i]!=t) ans += ss;
            c++;
            a[c] = height[i+1];
            b[c] = f[i]==t;
            ss += (LL)a[c] * b[c];
            while(a[c-1] >= a[c])
            {
                ss -= (LL)(a[c-1]-a[c]) * b[c-1];
                a[c-1] = a[c];
                b[c-1] += b[c];
                c--;
            }
        }
    }
    printf("%I64d\n",ans);
    
}

4、多个字符串的相关问题 多个字符串的相关问题 多个字符串的相关问题

先将所有的字符串连接起来,,中间用不相同的且没有出现在字符串中的字符隔开,然后求后缀数组和 height 数组,再利用 height 数组进行求解。这中间可能需要二分答案。

//主函数
int main()
{
    int T_T;
    scanf("%d", &T_T);
    while(T_T--)
    {
        int n_n;
        scanf("%d", &n_n);
        int len = 0;
        for(int i = 1; i <= n_n; i++)
        {
            scanf("%s", str);
            int k = strlen(str);
            for(int j = 0; j < k; j++)
            {
                r[j+len] = str[j] +10;
                who[j+len] = i;
            }
            r[len+k] = i;
            who[len+k] = 0;
            len += k+1;
        }
        len--;
        r[len] = 0;
        da(r, sa, len+1, 138);
        Cal_Height(r, sa, len);
        height[len+1] = -1;
        solve();
    }
}

不小于 k 个字符串中的最长子串

通过height数组进行分组,判断每组的后缀是否出现在不小于 k 个的原串中。
int who[maxn],yes[101]= {0},ii=0;
int n, nn, ss, ii;
int ans[maxn];

bool check(int mid, int len)
{
    bool flag = false;
    int j;
    for (int i = 2; i <= len; i=j+1)
    {
        while(height[i]= mid) j++;
        ii++;
        int s = 0;
        for (int k = i-1; k < j; k++)
            if ((t=who[sa[k]]) != 0)
                if (yes[t] != ii) yes[t]=ii,s++;
        if (s >= nn) 
        {
            if(flag) ans[++ss] = sa[i-1];
            else
            {
                ans[ss=1] = sa[i*1];
                flag = true;
            }
        }
    }
    return flag;
}
void solve(int flag, int len)
{
    int nn = n/2+1;
    int L = 1;
    int R = 1000;
    while (L <= R)
    {
        int mid = (L + R) >> 1;
        if (check(mid, len)) L = mid + 1;
        else R = mid -1;
    }
    if(flag) printf("\n");
    else flag = 1;
      
    if (L == 0) printf("?\n");
    else
    {
        for (int i = 0; i <= ss; i++)
        {
            int tmp = ans[i];
            for (int j = 0; j < R; j++)
                printf("%c", r[tmp+k]-100);
            printf("\n");
        }
    }
}

每个字符串至少出现两次且不重叠的最长子串

int who[maxn],min[11],max[11];
int len,n;
int check(int mid)
{
    int i,j,k;
    for(i=2; i<=len; i=j+1)
    {
        for(; height[i]=mid; j++);
        if(j-i+1max[who[sa[k]]]) max[who[sa[k]]]=sa[k];
        }
        for(k=1; k<=n; k++) if(max[k]-min[k]n) return 1;
    }
    return 0;
}
char st[110];

void solve()
{
    int L = 1;
    int R = 5000;
    while (L <= R)
    {
        int mid = (L + R) >> 1;
        if (check(mid)) L = mid + 1;
        else R = mid - 1;
    }
    printf("%d\n", R);
}

出现或反转后出现在每个字符串中的最长子串

//出现或反转后出现在每个字符串中的最长子串

你可能感兴趣的:(后缀数组2应用(内容总结自罗DADA论文))