[题解][笔记]lgP1368&最小表示法
原题链
算法用途
这个题就是要求在一个循环同构串中找出字典序最小的排列
算法过程
首先,因为这个数列可以把第一个元素一道最后一个去,所以我们把这个序列倍长,这样就不用对指针取模啥的,更加方便.
接着来讲算法的主体.首先我们先假设有一个排列:(7 5 3 3 3 1 4 6),把这个排列倍长就变成:(7 5 3 3 3 1 4 6 7 5 3 3 3 1 4 6),然后最小表示法是有两个指针(不能相等),先假设两个指针分别为(pos1),(pos2),那么这(pos1)指针就代表的是倍长后的序列从(pos1)作为开头,到(pos1 - 1)作为结尾的序列,(pos2)同理.接着我们先考虑(pos1)指针所指的数大于(pos2)指针所指的数的情况,这时显然以(pos2)为开头的排列更优所以更新(pos1),就是把(pos1+1),如果(pos1)所指的元素小于(pos2)所指的元素也是同理.
接下来重点讲讲如果(pos1)和(pos2)所指的元素是一样的怎么办.对于上面的序列,当(pos1=3),(pos2 = 4)时就出现了两个指针所指的元素相等的情况,这时我们无法判断两个指针谁代表的序列更优,所以(pos1)和(pos2)都往后跳一个,知道出现两个指针指着的元素不相等的时候为止,在举的例子中当两个指针向后跳了(2)次后所指的元素就不同了(实际实现的时候并不是直接变动指针的值,而是让指针加上一个向后移动的变化量).此时,(pos1=3+2=5,pos2=4+2=6),我们可以发现(pos2)所指的值小于(pos1)所指的值,所以(pos2)更优一些,并且在下标为(3~5)为开头的序列都不会是最有的,因为通过刚才的匹配我们得知下标为(6)为开头的才是最有的所以把(pos1)指针移动到下标(6)处.
这就是算法的基本过程,还没看懂的可以看看代码有一点点注释
代码实现
#include <bits/stdc++.h>
using namespace std;
int n;
int a[3000010];
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
a[i + n] = a[i];//把原序列倍长
}
int pos1 = 1,pos2 = 2,delta = 0,pos;
while(pos1 <= n && pos2 <= n){
delta = 0;//向后移动的变化量
while(a[pos1 + delta] == a[pos2 + delta])//如果指针指向的元素大小相等就一起往后跳
delta++;
if(a[pos1 + delta] > a[pos2 + delta])pos1 += delta + 1;//跳到当前的最优解
else pos2 += delta + 1;//调到当前的最优解
if(pos1 == pos2)pos2++;//两个指针不能相等
}
pos = min(pos1,pos2);//因为有可能会超过n所以去最小值
for(int i = pos;i <= n + pos - 1;i++){
printf("%d ",a[i]);
}
return 0;
}