最近做到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+k−1] 一定是大于等于 s[i] ,
因为如果其中有一个数小于 s[i] ,那么这个数一定在 s[j] 到 s[j+k−1] 中存在,
又因为必定有一个会在后面,所以如果 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(" "); } }
- 如果
s[i]>s[j]
,说明以
j
开始的字符串比较小,则