题解 P3809 【【模板】后缀排序】

jokers

2018-03-24 15:51:43

Solution

_**关于后缀数组**_ ------------ 后缀数组思路其实....不是非常难(炒鸡easy) 这里讲讲倍增算法 [可以在这里看一看经典思路图](https://blog.csdn.net/yxuanwkeith/article/details/50636898) 但是代码实现就特别难受... 三四个数组嵌套很难懂 所以发一篇自己大概(100%)看懂的代码 思路大概是 先基数排序 然后一层for(更新第二关键字(tp);利用这一轮的第一关键字更新下一轮的&&离散第一关键字(rk)) 当离散出来的计数==长度(n)是就o**k了 上代码! ```cpp #include <iostream> #include <cstdio> #include <cstring> #define inv inline void #define maxN 1000010 using namespace std; char s[maxN]; //s是字符串的读入 int a[maxN],rk[maxN],sa[maxN],tp[maxN],tax[maxN],n,m; //a是暂存数组,rk(第一关键字)第i位的排名,sa是排名为i的位置,tp是第二关键字辅助用的,tax是桶数组; inv read(){ scanf("%s",&s); n=strlen(s); for(int i=0;i<n;i++) a[i+1]=s[i]; //读入 } inv Rsort(){ for(int i=0;i<=m;i++) tax[i]=0; //tax是桶 //tax清零 for(int i=1;i<=n;i++) tax[rk[tp[i]]]++; //每一个出现的第一关键字++ for(int i=1;i<=m;i++) tax[i]+=tax[i-1]; //tax中i现在代表这个数至多能排第几位 for(int i=n;i>=1;i--) sa[tax[rk[tp[i]]]--]=tp[i]; //如果是第一遍就先不看这个 //就是把这一轮的sa做出来 //第二轮开始因为tp是按第二关键字进数组的 //所以从后往前来第二关键字肯定比前面小 //所以第一关键字相同时,第二关键字越大排名越后 //所以先拿到最大的值然后-- } inv Suffix(){ for(int i=1;i<=n;i++) rk[i]=a[i],tp[i]=i; //因为是第一轮所以直接用ascii码和位置当关键字; Rsort(); // for(int i=1;i<=n;i++) printf("%d ",sa[i]); for(int k=1;k<=n;k<<=1){ int num; num=0; for(int i=n-k+1;i<=n;i++) tp[++num]=i; //从n-k+1开始,到n的位置的第二关键字都为零,所以先入数组 for(int i=1;i<=n;i++) if(sa[i]>k) tp[++num]=sa[i]-k; //因为sa是排好序的,当sa[i]这个位置大于k时 //sa[i]就会作为别人的第二关键字,因为sa排好序的所以从小往大一个for就ok //所以就把sa[i]-k这个位置先进 //上面这两个for做完后tp就完成了 Rsort(); //再进行一次基数排序 swap(rk,tp); //用tp存下这一轮rk //下面开始更新下一轮rk rk[sa[1]]=1; //sa[1]的rk就是1 num=1; //计数器&&自带更新排名 for(int i=2;i<=n;i++) rk[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+k]==tp[sa[i-1]+k])?num:++num; //()里面的是比较第一和第二关键字是否相同 //()里面的如果成立,就直接等于num //如果不成立就++num并变成num++ if(num==n) break; //如果有n种排名就done了! m=num; //m代表tp的种类 } return; } int main(){ read(); m=122; Suffix(); for(int i=1;i<=n;i++) printf("%d ",sa[i]); // printf("\n"); // for(int i=1;i<=n;i++) printf("%d ",rk[i]); } ```