字符串最小表示法

最近做到bzoj2882这道题
因为最近在学 SAM ,所以一秒 SAM 做法
但是在用 SAM 的时候,由于结点数较多,我们要用 map 存储 SAM ,用 iterator 遍历 SAM
主要是博主比较弱,这些 STL 的工具基本都不会用,在网上找解决方法的时候,发现大部分解法都是用了一个新算法:字符串最小表示法O(n)算法

字符串的最小表示法

对于一个长度为n的字符串,可以将它的最后一位放到第一位来,依次类推,可以得到n种变形
字典序最小的一个变形

字符串比较一般是两两比较,所以我们设置两个指针: i,j
表示两个比较字符串的起点, k 表示已经比较完的长度

  • 如果s[i]<s[j],则 j++

    • 如果 s[i]>s[j] ,说明以 j 开始的字符串比较小,则i=j,j=i+1
    • 如果 s[i]=s[j] 的时候
    • 这时候有一个性质: i j之间的所有的字符一定是大于等于 s[i]
      k=0 ,循环寻找第一个 s[i+k]!=s[j+k] 的位置
      如果 s[i+k]<s[j+k] ,那么 j+=k+1

      为什么呢?

      首先 s[i] s[i+k1] 一定是大于等于 s[i]
      因为如果其中有一个数小于 s[i] ,那么这个数一定在 s[j] s[j+k1] 中存在,
      又因为必定有一个会在后面,所以如果 s[j] 先碰到了,那么一定不会继续到k的位置的,
      所以一定不存在比 s[i] 小的字符

      所以从其中的任意一个字符开始当作起始点,都不会比现在更小,所以只有从选出来的序列的后面那一个字符开始才有可能会是最小

      因此 j+=k+1

      如果序列中某个数和 s[i] 相等的话,那么一定会有之前或者以后在这个位置起始过,所以不需要再从这个位置进行起始

      如果 i==j 那么让 j++ 就可以回到原先的状态了
      最后只需要输出 i j最小的一个即可

      int getmin(char *s) 
      {
          int n=strlen(s);
          int i=0,j=1,k=0;
          while (i<n&&j<n&&k<n)
          {
              t=s[(i+k)%n]-s[(j+k)%n];
              if (!t) k++;
              else
              {
                  if (t>0) i+=k+1;
                  else j+=k+1;
                  if (i==j) j++;
                  k=0;
              }
          }
          return min(i,j);
      }

      bzoj2882 AC代码

      #include<cstdio>
      #include<cstring>
      #include<iostream>
      
      using namespace std;
      
      const int N=300005;
      int a[N],n; 
      
      int getmin()
      {
          int i=0,j=1,k=0;
          while (i<n&&j<n&&k<n)
          {
              int t=a[(i+k)%n]-a[(j+k)%n];
              if (!t) k++;
              else
              {
                  if (t>0) i+=k+1;
                  else j+=k+1;
                  if (i==j) j++;
                  k=0;
              }
          }
          return min(i,j);
      }
      
      int main()
      {
          scanf("%d",&n);
          for (int i=0;i<n;i++) scanf("%d",&a[i]);
          int t=getmin();
          for (int i=t;i<t+n;i++) {
              printf("%d",a[i%n]);
              if (i!=t+n-1) printf(" ");
          }
      }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值