bzoj 3230: 相似子串【SA+st表+二分】

总是犯低级错误,st表都能写错……
正反分别做一遍SA,预处理st表方便查询lcp,然后处理a[i]表示前i个后缀一共有多少个本质不同的子串,这里的子串是按字典序的,所以询问的时候直接在a上二分排名就能得到询问区间,然后用正反st表查lcp即可

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=200005;
int n,q,b[N],sa1[N],sa2[N],rk1[N],rk2[N],he1[N],he2[N],st1[20][N],st2[20][N],wa[N],wb[N],wv[N],wsu[N];
long long a[N];
char s[N];
long long read()
{
    long long r=0,f=1;
    char p=getchar();
    while(p>'9'||p<'0')
    {
        if(p=='-')
            f=-1;
        p=getchar();
    }
    while(p>='0'&&p<='9')
    {
        r=r*10+p-48;
        p=getchar();
    }
    return r*f;
}
bool cmp(int r[],int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
void saa(char r[],int n,int m,int sa[],int rk[],int he[])
{
    int *x=wa,*y=wb;
    for(int i=0;i<=m;i++)
        wsu[i]=0;
    for(int i=1;i<=n;i++)
        wsu[x[i]=r[i]]++;
    for(int i=1;i<=m;i++)
        wsu[i]+=wsu[i-1];
    for(int i=n;i>=1;i--)
        sa[wsu[x[i]]--]=i;
    for(int j=1,p=1;j<=n&&p<n;j<<=1,m=p)
    {
        p=0;
        for(int i=n-j+1;i<=n;i++)
            y[++p]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>j)
                y[++p]=sa[i]-j;
        for(int i=1;i<=n;i++)
            wv[i]=x[y[i]];
        for(int i=0;i<=m;i++)
            wsu[i]=0;
        for(int i=1;i<=n;i++)
            wsu[wv[i]]++;
        for(int i=1;i<=m;i++)
            wsu[i]+=wsu[i-1];
        for(int i=n;i>=1;i--)
            sa[wsu[wv[i]]--]=y[i];
        swap(x,y);
        x[sa[1]]=1;
        p=1;
        for(int i=2;i<=n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p:++p;
    }
    for(int i=1;i<=n;i++)
        rk[sa[i]]=i;
    for(int i=1,j,k=0;i<=n;he[rk[i++]]=k)
        for(k?k--:0,j=sa[rk[i]-1];r[i+k]==r[j+k];k++);
}
int ef(long long x)
{
    int l=1,r=n,ans=1;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(a[mid]>=x)
            r=mid-1,ans=mid;
        else
            l=mid+1;
    }
    return sa1[ans];
}
long long ques1(int x,int y)
{
    if(x==y)
        return n-x+1;
    int l=min(rk1[x],rk1[y])+1,r=max(rk1[x],rk1[y]),k=b[r-l+1];
    return min(st1[k][l],st1[k][r-(1<<k)+1]);
}
long long ques2(int x,int y)
{
    if(x==y)
        return n-x+1;
    int l=min(rk2[x],rk2[y])+1,r=max(rk2[x],rk2[y]),k=b[r-l+1];
    return min(st2[k][l],st2[k][r-(1<<k)+1]);
}
int main()
{
    scanf("%d%d%s",&n,&q,s+1);
    saa(s,n,200,sa1,rk1,he1);
    reverse(s+1,s+1+n);
    saa(s,n,200,sa2,rk2,he2);
    b[0]=-1;
    for(int i=1;i<=n;i++)
        b[i]=b[i>>1]+1;
    for(int i=1;i<=n;i++)
        st1[0][i]=he1[i],st2[0][i]=he2[i];
    for(int i=1;i<=17;i++)
        for(int j=1;j+(1<<i)-1<=n;j++)
        {
            st1[i][j]=min(st1[i-1][j],st1[i-1][j+(1<<(i-1))]);
            st2[i][j]=min(st2[i-1][j],st2[i-1][j+(1<<(i-1))]);
        }
    for(int i=1;i<=n;i++)
        a[i]=a[i-1]+n-sa1[i]+1-he1[i];
    // for(int i=1;i<=n;i++)
        // cerr<<sa1[i]<<" "<<he1[i]<<" "<<a[i]<<endl;
    while(q--)
    {
        long long x=read(),y=read();
        if(max(x,y)>a[n])
        {
            puts("-1");
            continue;
        }
        long long xl=ef(x),xr=xl+he1[rk1[xl]]-1+(x-a[rk1[xl]-1]),yl=ef(y),yr=yl+he1[rk1[yl]]-1+(y-a[rk1[yl]-1]),xx,yy;
        // cerr<<xl<<" "<<xr<<"   "<<yl<<" "<<yr<<endl;
        xx=min(min(xr-xl+1,yr-yl+1),ques1(xl,yl)),yy=min(min(xr-xl+1,yr-yl+1),ques2(n-xr+1,n-yr+1));
        printf("%lld\n",xx*xx+yy*yy);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/lokiii/p/10436246.html

时间: 2024-08-28 18:30:34

bzoj 3230: 相似子串【SA+st表+二分】的相关文章

BZOJ 3230: 相似子串( RMQ + 后缀数组 + 二分 )

二分查找求出k大串, 然后正反做后缀数组, RMQ求LCP, 时间复杂度O(NlogN+logN) --------------------------------------------------------------------- #include<cstdio> #include<algorithm> #include<cstring> #include<cctype> using namespace std; typedef long long

BZOJ 3230 相似子串 | 后缀数组 二分 ST表

BZOJ 3230 相似子串 题面 题解 首先我们要知道询问的两个子串的位置. 先正常跑一遍后缀数组并求出height数组. 对于每一个后缀suffix(i),考虑以i开头的子串有多少是之前没有出现过的,也就是考虑左端点在i.右端点在什么范围内时这个子串没有出现过--答案是右端点在[i + height[i] - 1, n]范围内时这个子串没出现过,即右端点在没有被"i与排在前一个的后缀的公共前缀"覆盖的部分时,这个子串没有出现过. 那么我们记录以每个i开头的新子串的数量,求前缀和,然

(ST表+二分+前缀和)CSU 1879 - Hack Protection

题意: 给定一个序列,求异或和与按位与和相同的区间有几个. 异或和:n个数异或起来.按位与和类似. 分析: 这才是神题,基础算法大杂烩. 问大佬这题的时候,人家只说很不难啊.. 只能说自己太菜. 由于询问区间个数,自然要快速知道某一个区间的异或和与按位与和. 异或和很简单,利用他的性质,直接求前缀和即可. 但是按位与没有和加法和异或类似的性质,无法直接求出. 但是利用之前的知识可以将所有结果预处理出来,而且只要nlogn的复杂度. 就是之前搞RMQ的ST表,很适合离线查询. 几乎不用动的把 mi

bzoj 3230 相似子串——后缀数组

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3230 作出后缀数组,从 LCP 看每个位置对于本质不同子串的贡献,而且他们已经按前面部分排好序了,所以直接在 sa[ ] 上二分就能找到询问的子串. 找最长公共前缀就用 ht[ ] 和子串的长度比较就行.找最长公共后缀就一开始把原串翻转,做出翻转后的 ht[ ] ,就能查询了. 也可以二分一个最长公共后缀的位置,然后用正常的 ht[ ] 判断. 注意 long long . #includ

2019 CCPC 网络赛第三题 K-th occurrence 后缀数组+划分树+ST表+二分

题意:给你一个长度为n的字符串,每次询问给出三个数:L , R , K,表示原串 L 到 R 的子串在原串第K次出现的首字母的位置 解题思路:对子串的大量操作,不难想到后缀数组(后缀树/后缀自动机不会,所以没想到),注意到子串s[L.....R]必然是某一个后缀的前缀,所以所有前缀是该子串的后缀的排名(即rank数组的值)必定连续,也就是说在后缀数组(sa数组)中,下标是连续的,那么就是求区间第K大了(因为sa数组的值代表的是在字符串中的位置)(这里区间第K大我用划分树求),至于这一段区间的起点

BZOJ 1396:识别子串 SA+树状数组+单调队列

1396: 识别子串 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 381  Solved: 243[Submit][Status][Discuss] Description Input 一行,一个由小写字母组成的字符串S,长度不超过10^5 Output L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长. Sample Input agoodcookcooksgoodfood Sample Output 1 2 3 3

Codeforces Round #371 (Div. 1) D - Animals and Puzzle 二维ST表 + 二分

D - Animals and Puzzle #include<bits/stdc++.h> #define LL long long #define fi first #define se second #define mk make_pair #define PII pair<int, int> #define PLI pair<LL, int> #define ull unsigned long long using namespace std; const in

K-th occurrence(后缀树组+划分树+ST表+RMQ+二分)

2019CCPC网络选拔赛1003 HDU6704 题目大意: T个测试样例.一个长度为N的字符串S,之后Q个[l,r,k],表示一个子串S[l,r],求出第k个该子串的下标.起始坐标为1.不存在输出-1. 数据范围:1≤T≤20,  1≤N≤105,  1≤Q≤105,  1≤l≤r≤N,  1≤k≤N,  |S|=N; 赛后补题.参考题解说后缀树组+划分树+ST表+二分. 比赛的时候只会后缀树组不会划分树,赛后仔细想,觉得后缀数组可以,然而并不,会TLE. 补提的时候先是采用后缀树组+划分树

[LuoguP4094] [HEOI2016] [TJOI2016]字符串(二分答案+后缀数组+ST表+主席树)

[LuoguP4094] [HEOI2016] [TJOI2016]字符串(二分答案+后缀数组+ST表+主席树) 题面 给出一个长度为\(n\)的字符串\(s\),以及\(m\)组询问.每个询问是一个四元组\((a,b,c,d)\),问\(s[a,b]\)的所有子串和字符串\(s[c,d]\)的最长公共前缀长度的最大值. \(n,m \leq 10^5\) 分析 显然答案有单调性.首先我们二分答案\(mid\),考虑如何判定. 如果mid这个答案可行,那么一定存在一个后缀x,它的开头在\([a,