0
点赞
收藏
分享

微信扫一扫

字符串算法之最小表示法

伢赞 2022-02-18 阅读 100
算法

问题描述

  • 一组循环同构的字符串,问字典序最小的表示方法

循环同构

  • 设有两个字符串 S , T S,T S,T,如果 S S S中存在某个位置满足 S [ i . . . n ] + S [ 1... i − 1 ] = T S[i...n]+S[1...i-1]=T S[i...n]+S[1...i1]=T,则称 S S S T T T循环同构

解决方案

  • 暴力显然是 O ( n 2 ) O(n^2) O(n2)

下面介绍最小表示法

  • 假设 A , B A,B A,B均为字符串 S S S的某一子串,起点分别为 i , j ( i < j ) i,j(i\lt j) i,j(i<j),假设存在某个 k k k满足 A [ i . . . i + k − 1 ] = B [ j . . . j + k − 1 ] A[i...i+k-1]=B[j...j+k-1] A[i...i+k1]=B[j...j+k1],换句话说,这两个字符串的前 k k k个字符相同,如果有 A [ i + k ] > B [ j + k ] A[i+k]\gt B[j+k] A[i+k]>B[j+k],那么字符串起点在 [ i , i + k ] [i,i+k] [i,i+k]之间的所有情况都不可能成立为这组循环同构的字符串的字典序最小的表示方法,因为显然 B B B字符串的那种情况要优于 A A A,这就是最小表示法的核心思路; A [ i + k ] < B [ j + k ] A[i+k]\lt B[j+k] A[i+k]<B[j+k]时同理;相等的时候就都可以
  • 根据上述思路容易得到代码,下面通过几个题目说明

例题 1

https://www.luogu.com.cn/problem/P1368

  • 模板,找循环同构的数组中字典序最小的
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n;
  cin >> n;
  vector<int> a(n);
  for(int i=0;i<n;i++){
    cin >> a[i];
  }
  a.insert(a.end(), a.begin(), a.end());
  int p1 = 0;
  int p2 = 1;
  while(p1 < n && p2 < n){
    int k = 0;
    while(k < n && a[p1 + k] == a[p2 + k]) k += 1;
    if(k == n) break;// 走了一圈,说明整个字符串都是相同的字符,可以反证法证明得到
    if(a[p1 + k] > a[p2 + k]){
      p1 += k + 1;
    }else{
      p2 += k + 1;
    }
    if(p1 == p2) p2 += 1;// 相同的起点没必要比较
  }
  int p = min(p1, p2);// 最小值即为字典序最小的起点
  for(int i=p;i<n+p;i++){
    cout << a[i] << " \n"[i == n + p - 1];
  }
  return 0;
}

例题 2

https://www.luogu.com.cn/problem/P1709

  • 水两道蓝题美滋滋。。。这个算法似乎也可以使用后缀数组或者 l c p lcp lcp等方法解决
  • 注意读入不能直接 c i n cin cin,要一个字符一个字符的读入,因为字符串是分行的
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  string s;
  int n;
  cin >> n;
  s.resize(n);
  for(int i=0;i<n;i++) cin >> s[i];
  s += s;
  function<int(int)> solve = [&](int n){
    int p1 = 0;
    int p2 = 1;
    while(p1 < n && p2 < n){
      int k = 0;
      while(k < n && s[p1 + k] == s[p2 + k]) k += 1;
      if(k == n) break;
      if(s[p1 + k] > s[p2 + k]){
        p1 += k + 1;
      }else{
        p2 += k + 1;
      }
      if(p1 == p2) p2 += 1;
    }
    return min(p1, p2);
  };
  cout << solve(n);
  return 0;
}

例题 3

  • 最后看一道题,这个算法原理简单,只要理解了别忘,可能会很有用

https://www.acwing.com/problem/content/description/160/

  • 问两个字符串的最小表示是不是相同
  • 有人用哈希+二分 l c p lcp lcp过了,最小表示法直接秒解
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  string s1, s2;
  cin >> s1 >> s2;
  int n = s1.length();
  s1 += s1;
  s2 += s2;
  function<int(string &, int n)> Get_Min = [&](string &s, int n){
    int p1 = 0;
    int p2 = 1;
    while(p1 < n && p2 < n){
      int k = 0;
      while(k < n && s[p1 + k] == s[p2 + k]) k += 1;
      if(k == n) break;
      if(s[p1 + k] > s[p2 + k]) p1 += k + 1;
      else p2 += k + 1;
      if(p1 == p2) p2 += 1;
    }
    return min(p1, p2);
  };
  int a = Get_Min(s1, n);
  int b = Get_Min(s2, n);
  if(s1.substr(a, n) == s2.substr(b, n)){
    cout << "Yes\n" << s1.substr(a, n) << '\n';
  }else{
    cout << "No\n";
  }
  return 0;
}
举报

相关推荐

0 条评论