码迷,mamicode.com
首页 > 编程语言 > 详细

后缀数组Da模板+注释 以及 dc3模板

时间:2020-02-17 16:23:51      阅读:79      评论:0      收藏:0      [点我收藏+]

标签:情况   amp   区间   can   应该   class   lan   长度   string   

后缀数组Da模板:

  1 /*
  2 后缀数组倍增法Da板子
  3 */
  4 #include <cstdlib>
  5 #include <cstring>
  6 #include <cstdio>
  7 #include <algorithm>
  8 using namespace std;
  9 const int N = 200000+9;
 10 int c[N];
 11 int rank[N], height[N];
 12 int sa[N],s[N],n;
 13 /*
 14 rank[i]数组的下标表示后缀是[i...n],它的值是这个后缀的排名
 15 sa[i]数组的下标表示这个后缀的排名,它的值是这个后缀在这个串中的起始位置。譬如sa[i]表示的后缀就是[sa[i]...n]
 16 c是辅助数组
 17 height[i]数组表示排名第i的后缀和排名第i-1后缀的最长公共前缀
 18 s数组放的是输入的数
 19 */
 20 bool pan(int *x,int i,int j,int k,int n)
 21 {
 22     int ti=i+k<n?x[i+k]:-1;
 23     int tj=j+k<n?x[j+k]:-1;
 24     return x[i]==x[j]&&ti==tj;
 25 }
 26 //下面这个代码是未优化版代码讲解
 27 //void build_SA(int m)
 28 //{
 29 //    int i,j,k;
 30 //    //清空数组,m就是一个范围。是根据你输入字符大小来决定
 31 //    for(i=1; i<=m; ++i) c[i]=0;
 32 //    //将原数组里面内容复制一份
 33 //    for(i=0; i<n; ++i) x[i]=s[i];
 34 //    //这个就是统计输入数组里面某一个字符出现次数
 35 //    for(i=0; i<n; ++i) c[x[i]]++;
 36 //    //求前缀和
 37 //    for(i=2; i<=m; ++i) c[i]+=c[i-1];
 38 //    //这行代码就是按照每一个后缀的第一个字母排序
 39 //    for(i=n-1; i>=0; i--) sa[--c[x[i]]]=i;
 40 //    //k表示关键字的长度
 41 //    for(int k=1; k<=n; k<<=1)
 42 //    {
 43 //        int num=0;
 44 //        //当k==1的时候是对每一个后缀得第二个字符排序
 45 //        for(i=n-k; i<n; ++i)//当k==1的时候很明显最后一个后缀(即[(n-1)...(n-1)])没有第二个字符
 46 //            y[num++]=i; //所以仅仅按照第二字符排序的时候他的排名必定靠前
 47 //
 48 //        //下面这个for循环就是对剩下的后缀进行排序
 49 //        for(i=0; i<n; ++i)
 50 //        {
 51 //            if(sa[i]>=k) //他的排序借助了我们对第一个字符排序后的数组
 52 //                y[num++]=sa[i]-k;  //这里为啥要减去k,因为我们要对每一个后缀得第二个字符排序,可是
 53 //            //y数组的值代表这个后缀从哪开始
 54 //        }
 55 //        for(i=1; i<=m; ++i) c[i]=0;
 56 //        for(i=0; i<=n; ++i) c[x[i]]++;
 57 //        for(i=2; i<=m; ++i) c[i]+=c[i-1]; //这三行和上面一样,因为在排序过程中c数组的值改变了,所以我们还要重新弄一遍
 58 //        //下面一行代码就将每一个后缀得第一个字符和第二个字符综合起来给每一个后缀排序
 59 //        for(i=n-1; i>=0; --i) sa[--c[x[y[i]]]]=y[i],y[i]=0;
 60 //        /*
 61 //        上面一行代码的作用就是用结合两个关键字把总的排序搞出来
 62 //        我们应该做的,就是先根据第一关键字排序,第一关键字相等时根据第二关键字大小排序。
 63 //        但是看上去,只进行了一次计数排序啊。
 64 //        还记得这个计数排序的特点:先根据x的值排序,x值相等时根据出现先后次序排序。
 65 //        x里面存了上次关键字的排序,在本次排序中即是第一关键字的排序,x的值排序==第一关键字排序,这里的计数排序做的是对的。那么第二关键字呢?
 66 //        前面对第二关键字进行了排序,在这里x[y[i]]就是根据第二关键字的顺序重新改变了第一关键字的顺序,也就是说在本次计数排序中,出现先后次序排序==第二关键字大小排序。
 67 //        换句话说,我们先单独对第二关键字排序,根据这个顺序改变第一关键字的顺序,由于在计数排序时首先按照第一关键字的值排序,而第一关键字的值没有改变所以首先还是根据第一关键字排序,
 68 //        改变的是第一关键字相同的时候,出现在前面的第二关键字排在前面。
 69 //
 70 //        y[i]的值就是排名第i的后缀是【y[i]...(n-1)】,那么x[y[i]]就代表了y[i]这个位置上的字符,这就做到了
 71 //        “先根据第一关键字排序,第一关键字相等时根据第二关键字大小排序。”
 72 //        */
 73 //        /*
 74 //        做到这里就完成了第一第二关键字的合并,得到了合并以后的关键字顺序,它可以用于下次迭代。
 75 //        对于下面代码每次新的迭代要用到rank数组x,由于有了刚求的关键字排序数组sa,要得到rank数组也很容易。
 76 //        但是对于相同的值,rank应该相同,所以要判断一下合并以后的关键字是否相同。
 77 //        */
 78 //        swap(x,y);
 79 //        num=1;
 80 //        x[sa[0]]=1;
 81 //        for(i=1; i<n; ++i)
 82 //        {
 83 //            if(y[sa[i]]!=y[sa[i-1]] || y[sa[i]+k]!=y[sa[i-1]+k])
 84 //                x[sa[i]]=++num;
 85 //            else x[sa[i]]=num;
 86 //        }
 87 //        if(num>=n) break;
 88 //        m=num;
 89 //    }
 90 //}
 91 void build_SA(int n,int r)
 92 {
 93     int *x=rank,*y=height;
 94     //先清空辅助数组
 95     for(int i=0; i<r; i++)c[i]=0;
 96     //因为后面要排序,所以先统计一下各数字个数
 97     for(int i=0; i<n; i++)c[s[i]]++;
 98     //求前缀和,可能你不知道为啥要求前缀和。这个一言两语说不清。建议模拟一下
 99     for(int i=1; i<r; i++)c[i]+=c[i-1];
100     //前面辅助工作做完后,下面这一行代码就对每一个后缀按第一个关键字排序了(模拟一下)
101     //第一关键字?比如后缀是abbcd那么a就是第一关键字,c就是第四关键字,d就是第五关键字,二三关键字都是b
102     for(int i=n-1; i>=0; i--)sa[--c[s[i]]]=i;
103 
104     r=1;
105     x[sa[0]]=0;
106     for(int i=1; i<n; i++)  //这个x数组就是整理一下对每一个后缀第一个关键字排序的结果
107     //怎么整理?就是如果两个后缀第一关键字相同,那么排名也一样,否则就不一样,排名是从0到n
108         x[sa[i]]=s[sa[i]]==s[sa[i-1]]?r-1:r++;
109 
110     for(int k=1; r<n; k<<=1)  //这个里面内容和未优化代码中的差不多
111     {
112         int yn=0;
113         //y数组里面保存着对每一个后缀的第二个关键字排序的结果
114         //注意这个时候仅仅是按照第二个关键字排序,第一个关键字没有影响这个排序
115         for(int i=n-k; i<n; i++)y[yn++]=i;
116         for(int i=0; i<n; i++)
117             if(sa[i]>=k)y[yn++]=sa[i]-k;
118         //这一部分是对每一个后缀按照第一二关键字同时排序
119         //这样的话就是先判断第一关键字相同不,相同的话再按照第二关键字排序
120         for(int i=0; i<r; i++)c[i]=0;
121         for(int i=0; i<n; i++)++c[x[y[i]]];
122         for(int i=1; i<r; i++)c[i]+=c[i-1];
123         for(int i=n-1; i>=0; i--)sa[--c[x[y[i]]]]=y[i];
124         swap(x,y);
125         r=1;
126         x[sa[0]]=0;
127         for(int i=1; i<n; i++)  //这个就是将第一第二关键字排序结果当作新的第一关键字,继而进行倍增
128             x[sa[i]]=pan(y,sa[i],sa[i-1],k,n)?r-1:r++;
129     }
130     for(int i=0; i<n; i++)rank[i]=x[i];
131 }
132 //height[i]数组表示排名第i的后缀和排名第i-1后缀的最长公共前缀
133 /*
134 能够线性计算height[]的值的关键在于h[](height[rank[]])的性质,即h[i]>=h[i-1]-1,下面具体分析一下这个不等式的由来。
135 我们先把要证什么放在这:对于第i个后缀,设j=sa[rank[i] – 1],也就是说j是i的按排名来的上一个字符串,按定义来i和j的最
136 长公共前缀就是height[rank[i]],我们现在就是想知道height[rank[i]]至少是多少,而我们要证明的就是至少是
137 height[rank[i-1]]-1。好啦,现在开始证吧。
138 
139 首先我们不妨设第i-1个字符串(这里以及后面指的“第?个字符串”不是按字典序排名来的,是按照首字符在字符串中的位置来的)
140 按字典序排名来的前面的那个字符串是第k个字符串,注意k不一定是i-2,因为第k个字符串是按字典序排名来的i-1前面那个,
141 并不是指在原字符串中位置在i-1前面的那个第i-2个字符串。
142 这时,依据height[]的定义,第k个字符串和第i-1个字符串的公共前缀自然是height[rank[i-1]],现在先讨论一下第k+1个字符串
143 和第i个字符串的关系。
144 第一种情况,第k个字符串和第i-1个字符串的首字符不同,那么第k+1个字符串的排名既可能在i的前面,也可能在i的后面,但没有
145 关系,因为height[rank[i-1]]就是0了呀,那么无论height[rank[i]]是多少都会有height[rank[i]]>=height[rank[i-1]]-1,也
146 就是h[i]>=h[i-1]-1。
147 第二种情况,第k个字符串和第i-1个字符串的首字符相同,那么由于第k+1个字符串就是第k个字符串去掉首字符得到的,第i个字
148 符串也是第i-1个字符串去掉首字符得到的,那么显然第k+1个字符串要排在第i个字符串前面,要么就产生矛盾了。同时,第k个
149 字符串和第i-1个字符串的最长公共前缀是height[rank[i-1]],那么自然第k+1个字符串和第i个字符串的最长公共前缀就是
150 height[rank[i-1]]-1。
151 到此为止,第二种情况的证明还没有完,我们可以试想一下,对于比第i个字符串的字典序排名更靠前的那些字符串,谁和
152 第i个字符串的相似度最高(这里说的相似度是指最长公共前缀的长度)?显然是排名紧邻第i个字符串的那个字符串了呀,
153 即sa[rank[i]-1]。也就是说sa[rank[i]]和sa[rank[i]-1]的最长公共前缀至少是height[rank[i-1]]-1,那么就有
154 height[rank[i]]>=height[rank[i-1]]-1,也即h[i]>=h[i-1]-1。
155 证明完这些之后,下面的代码也就比较容易看懂了。
156 
157 我们说了这么多就是为了证明height[i]>=height[i-1]-1,证明了这个我们就不用一个后缀和好多后缀比较。可以直接从
158 height[i-1]-1来比较
159 */
160 void get_height(int n)
161 {
162     int i,j,k=0;
163     for(i=1; i<=n; i++)rank[sa[i]]=i;
164     for(i=0; i<n; i++)
165     {
166         if(k)k--;
167         else k=0;
168         j=sa[rank[i]-1];
169         while(s[i+k]==s[j+k])k++;
170         height[rank[i]]=k;
171     }
172 }
173 int main()
174 {
175     scanf("%d",&n);
176     for(int i=0; i<n; i++)
177         scanf("%d",&s[i]),s[i]++;
178     s[n]=0;
179     build_SA(n+1,N);
180     get_height(n);
181     /*
182     注意,用这个板子的时候都要在输入的最后新加一个长度(必须加,要不然会跑不出来结果),然后像这一个板子尽量新加的
183     那个数比所有输入的数都要小。
184     这样的话排名第0的后缀肯定是[n...n]
185 
186     然后主串的排名是1到n,它的height数组范围就是2到n了(这里的范围都是闭区间)
187     */
188     for(int i=2;i<=n;++i)
189     {
190         printf("%d ",height[i]);
191     }
192     printf("\n");
193     return 0;
194 }

学习后缀数组链接:

https://www.cnblogs.com/nietzsche-oier/articles/6621881.html

https://blog.csdn.net/qq_37774171/article/details/81776029

 

后缀数组dc3模板:

//后缀数组dc3模板
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 3000010;
#define F(x) ((x) / 3 + ((x) % 3 == 1 ? 0 : tb))
#define G(x) ((x) < tb ? (x) * 3 + 1 : ((x) - tb) * 3 + 2)
int wa[maxn], wb[maxn], Ws[maxn], wv[maxn], sa[maxn];
int Rank[maxn], height[maxn],r[maxn];
char s[maxn];
int c0(int *r, int a, int b)
{
    return r[a] == r[b] && r[a + 1] == r[b + 1] && r[a + 2] == r[b + 2];
}
int c12(int k, int *r, int a, int b)
{
    if (k == 2)
        return r[a] < r[b] || r[a] == r[b] && c12(1, r, a + 1, b + 1);
    return r[a] < r[b] || r[a] == r[b] && wv[a + 1] < wv[b + 1];
}
void Rsort(int *r, int *a, int *b, int n, int m)
{
    for (int i = 0; i < n; i++) wv[i] = r[a[i]];
    for (int i = 0; i < m; i++) Ws[i] = 0;
    for (int i = 0; i < n; i++) Ws[wv[i]]++;
    for (int i = 1; i < m; i++) Ws[i] += Ws[i - 1];
    for (int i = n - 1; i >= 0; i--) b[--Ws[wv[i]]] = a[i];
}
void dc3(int  *r,int *sa,int n, int m)
{
    int i, j, *rn = r + n, *san = sa + n, ta = 0, tb = (n + 1) / 3, tbc = 0, p;
    r[n] = r[n + 1] = 0;
    for (i = 0; i < n; i++) if (i % 3 != 0) wa[tbc++] = i;
    Rsort(r + 2, wa, wb, tbc, m);
    Rsort(r + 1, wb, wa, tbc, m);
    Rsort(r, wa, wb, tbc, m);
    for (p = 1, rn[F(wb[0])] = 0, i = 1; i < tbc; i++)
        rn[F(wb[i])] = c0(r, wb[i - 1], wb[i]) ? p - 1 : p++;
    if (p < tbc) dc3(rn, san, tbc, p);
    else for (i = 0; i < tbc; i++) san[rn[i]] = i;
    for (i = 0; i < tbc; i++) if (san[i] < tb) wb[ta++] = san[i] * 3;
    if (n % 3 == 1) wb[ta++] = n - 1;
    Rsort(r, wb, wa, ta, m);
    for (i = 0; i < tbc; i++) wv[wb[i] = G(san[i])] = i;
    for (i = 0, j = 0, p = 0; i < ta && j < tbc; p++)
        sa[p] = c12(wb[j] % 3, r, wa[i], wb[j]) ? wa[i++] : wb[j++];
    for (; i < ta; p++) sa[p] = wa[i++];
    for (; j < tbc; p++) sa[p] = wb[j++];
}
void get_height(int n)
{
    int i, j, k = 0;
    for (i = 1; i <= n; i++) Rank[sa[i]] = i;
    for (i = 0; i < n; height[Rank[i++]] = k)
        for (k ? k-- : 0, j = sa[Rank[i] - 1]; r[i + k] == r[j + k]; k++);
}
int main()
{
    while(~scanf("%s",s))
    {
        int n=strlen(s);
        if(n==1 && s[0]==.) break;
        for(int i=0;i<n;++i)
            r[i]=s[i];
        r[n]=0;
        /*
        这个用法和后缀数组Da模板一样,后面要加一个小于输入所有字符的字符
        */
        dc3(r,sa,n+1,200);
        get_height(n);
        for(int i=2;i<=n;++i)
            printf("%d ",height[i]);
        printf("\n");
    }
    return 0;
}

dc3模板学习链接:

https://www.cnblogs.com/jianglangcaijin/p/6035937.html

 

后缀数组Da模板+注释 以及 dc3模板

标签:情况   amp   区间   can   应该   class   lan   长度   string   

原文地址:https://www.cnblogs.com/kongbursi-2292702937/p/12321985.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!