首页 > 其他分享 >后缀排序

后缀排序

时间:2022-10-09 00:34:18浏览次数:38  
标签:后缀 rank int init 循环 排序

做了 ABC270F,茅塞顿开。后缀排序,分明是抄的循环移位排序板子。

ABC270F

题意:给定两个长度为 \(n\) 的字符串 \(s,t\),求有多少个有序点对 \((i,j)\) 使得 \((s \rightarrow i) < (t \rightarrow j)\)。其中 \((s \rightarrow i)\) 表示 \(s\) 向右循环位移 \(i\) 位。其中 \(i,j \in [0,n-1]\)。

\(n \le 2 \times 10^5\)

分析:首先转化题意。将所有循环移位之后的字符串混在一起排序,所需要寻找的就是 \(rank_{(s\rightarrow i)} \le rank_{(t\rightarrow j)}\) 的个数。也就是说,混排找到 \(rank\) 就完事了。

其实后缀排序抄的循环移位排序板子。循环移位是本质。

思想就是要利用好“循环”的性质:以 \(c_i\) 开头,长度为 \(2^k\) 的字符串的后半部分也就是以 \(c_{i + 2^{k-1}}\) 开头,长度为 \(2^{k-1}\) 的字符串。

定义“第 \(i\) 层”为排好以每个位置为开头, \(2^i\) 为长度的字符串。这时我们就可以利用上一层求出的 rank,求出这一层一个 pair,也就是左半边和右半边的 rank,其中右半边的 rank 是从别的地方拿来的(合理利用信息)。

注意并不一定是 \(n=2^k\) 的才可以排序,我们最后一次排序可以类似 ST 表,有一定的重合,比如 \(n=6\),那么我们使用 \(1234~3456\) 作为上一层的 pair 的来源。可以证明这个是对的。(怎么证明:考虑讨论两个字符串中是第几位存在大小不一样的关系,然后分别看看能不能检验出来)

然后算答案,有可能会踩坑(atcoder 的数据水的要命,踩坑了也能过几十个点)。正确的做法是,先把 \(s\) 里所有 rnk 拍到桶里做前缀和,然后用 \(t\) 里的元素去找。

赛时写的是没有优化的 \(O(n \log^2 n)\) 算法。如果用基数排序可以做到 \(O(n \log n)\)。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
int n; 
int rnk[500010];
int pow2[500010];
vector<pair<char, int>> init;
vector<pair<pair<int, int>, int>> pk;  //后缀排序用的
int calc(int i, int len) {return (i<n ? (i+len)%n : n+((i-n+len)%n));}
int cc[500010];
int ss[500010];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    time_t start = clock();
    //think twice,code once.
    //think once,debug forever.
    cin >> n;
  //  cout << calc(4, 2);
    pow2[0]=1;
    f(i,1,n)pow2[i]=pow2[i-1]*2;
    string s,t;cin>>s>>t;
    s += t;
    int x = log2(n);
    //重新标号,从 0~n-1,n~2n-1
    //len=1
    f(i,0,2*n-1){
        init.push_back({s[i],i});
    }
    sort(init.begin(),init.end());
    int now=0;
    f(i,0,2*n-1){
        if(i != 0 && init[i].first == init[i-1].first) rnk[init[i].second]=rnk[init[i-1].second];
        else rnk[init[i].second]=++now;
    }
  //  cout << "k=0"<<endl;
 //   f(i,0,2*n-1)cout<<rnk[i]<<" \n"[i==2*n-1];
    f(k, 0, x - 1){
        //排序长度为2^k
        int len=pow2[k];
      //  cout << len << endl;
        f(i,0,2*n-1){
            pk.push_back({{rnk[i],rnk[(i<n ? (i+len)%n : n+((i-n+len)%n))]}, i});
        }
        sort(pk.begin(),pk.end());
        now=0;
        f(i,0,2*n-1){
            if(i!=0&&pk[i].first==pk[i-1].first) rnk[pk[i].second]=rnk[pk[i-1].second];
            else rnk[pk[i].second]=++now;
        }
        pk.clear();
       // cout << "k="<<k<<endl;
      //  f(i,0,2*n-1)cout<<rnk[i]<<" \n"[i==2*n-1];
    }
    int lft = n - pow2[x];
    f(i,0,2*n-1){
        pk.push_back({{rnk[i],rnk[(i<n ? (i+lft)%n : n+((i-n+lft)%n))]}, i});
    }    
    sort(pk.begin(),pk.end());
    now=0;
    f(i,0,2*n-1){
        if(i!=0&&pk[i].first==pk[i-1].first) rnk[pk[i].second]=rnk[pk[i-1].second];
        else rnk[pk[i].second]=++now;
    }
    f(i,0,n-1)cc[rnk[i]]++;
   // f(i, 0, 2*n-1) cout<<rnk[i]<<" \n"[i==2*n-1];
   // cout<<now<<endl;
    f(i, 1, now) ss[i] = ss[i-1]+cc[i];
    int ans=0;
    f(i, n,2*n-1)ans+=ss[rnk[i]];
    cout<<ans<<endl;
    time_t finish = clock();
    //cout << "time used:" << (finish-start) * 1.0 / CLOCKS_PER_SEC <<"s"<< endl;
    return 0;
}

后缀排序

后缀排序为什么抄的循环节排序?

考虑后缀和循环的差别是什么。后缀结束了就不会有下一个了,于是例如 \(ab\) 和 \(a\) 比较的时候因为 \(a\) 的第二位没有了,所以直接判定 \(a\) 比较小。这一点如果用循环移位在 \(a\) 之后加上个什么东西可就两说了。

所以我们为了能够统一每个字符串的长度,在每个后缀结束的时候加上一个 \(\$\) 符号,表示一个比所有字母都小的符号。然后把后面的循环补上来,可以发现最终排序的就是一堆循环移位字符串。

标签:后缀,rank,int,init,循环,排序
From: https://www.cnblogs.com/Zeardoe/p/16770755.html

相关文章

  • 冒泡排序
    voidsort(intarr[],intM)//注:数组传参过程中传过去的不是整个数组而是首元素的地址;//所以计算元素个数只能在主函数中进行;{//确定冒泡排序的趟数:通过推断可知为元素数-1......
  • 冒泡排序
    冒泡排序代码importjava.util.Arrays;publicclassBubbleSort{publicstaticvoidmain(String[]args){int[]numbers={5,1,3,2,4,6};......
  • 20 -- 排序算法之基数排序
        举例说明:1、第一轮:按照每个元素的 个 位取出,然后将这个数放在对应的桶(数组)中;在按照这个桶的顺序,放入原来的数组中-->  2、第二轮:按照每个元素的十 ......
  • 【Java基础】Collections集合概述和使用、ArrayList集合存储学生并排序及斗地主案例
    目录​​一、Collections概述和使用​​​​二、ArrayList集合存储学生并排序​​​​三、斗地主案例​​一、Collections概述和使用Collection类的作用:是针对集合操作的工......
  • 【Java基础】TreeSet集合、自然排序、比较器排序、成绩排序及不重复随机数案例
    目录​​一、TreeSet集合概述和特点​​​​二、自然排序Comparable的使用​​​​三、比较器排序Comparator的使用​​​​四、成绩排序案例​​​​五、不重复的随机数案......
  • IO流案例,集合到文件数据排序、复制单级和多级文件夹及复制文件的异常处理
    目录​​一、集合到文件数据排序​​​​二、复制单级文件夹​​​​三、复制多级文件夹​​​​四、复制文件的异常处理​​​​基本做法:​​​​JDK7版本改进:​​​​JDK9......
  • PHP 多维数组排序学习
    <?php$content_a['score']=3;$content_a['name']='3name';$content_b['score']=6;$content_b['name']='3name';$list1[]=$content_a;$list1[]=$content_b;print......
  • 冒泡排序、选择排序 、快速排序 、
    1.冒泡排序每一趟只能确定将一个数归位。即第一趟只能确定将末位上的数归位,第二趟只能将倒数第2位上的数归位,依次类推下去。如果有n个数进行排序,只需将n-1个数归......
  • 冒泡排序算法并调优
    算法步骤比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。......
  • 数组-算法-排序
    定义数组publicstaticvoidmain(String[]args){  //我们的数组必须初始化,才能使用  //动态出初始化:接受由我们指定的长度,由系统赋初始值  int[]arr=......