P1368 最小表示法

P1368 最小表示法
时间限制:1000MS 内存限制:250.00MB

描述

小敏和小燕是一对好朋友。
他们正在玩一种神奇的游戏,叫 Minecraft。
他们现在要做一个由方块构成的长条工艺品。但是方块现在是乱的,而且由于机器的要求,他们只能做到把这个工艺品最左边的方块放到最右边。
他们想,在仅这一个操作下,最漂亮的工艺品能多漂亮。
两个工艺品美观的比较方法是,从头开始比较,如果第 i 个位置上方块不一样那么谁的瑕疵度小,那么谁就更漂亮,如果一样那么继续比较第 i+1 个方块。
如果全都一样,那么这两个工艺品就一样漂亮。

输入

第一行一个整数 n,代表方块的数目。(1<=n<=300000)
第二行 n 个整数,每个整数按从左到右的顺序输出方块瑕疵度的值。

输出

一行 n 个整数,代表最美观工艺品从左到右瑕疵度的值。

样例输入

10
10 9 8 7 6 5 4 3 2 1

样例输出

1 10 9 8 7 6 5 4 3 2

思路

最小表示法模板题
该题目本质和字符串的在循环同构串里找出字典序最小的那个字符串一样
最小表示法,是找最终的那个字符串的开头字符所在的位置,然后从该位置循环输出整个字符串。
设字符串为
/
S[100000]; //从S[0]开始存储数据;
/
为了找到这个字符的位置,大致思路是,设 i=0 和 j=1 遍历一遍字符串, 用i和j其中一个的来记录 遍历到当前这个位置时 字典序小的字符位置,当遍历到与当前记录的字典序最小的字符相同的字符时(即 S[i] == S[j] ),则用k++来比较以 S[i] 和 S[j] 为开头,长度为k的字符串的字典序大小,即挨个比较 S[i+k] 和 S[j+k] 的大小。
当然,如果两个字符串比较到 k == len 时(len为字符串长度),则该整个字符串S是循环的,所以直接从 S[i] 和S [j] 其中一个为起点输出字符串即可因为再往后遍历,都还是一样的结果,都是字符串都是循环的。
因此,用i和j遍历字符串的时候会有三个情况:
(因为当比较单个字符大小的时候k=0,所以 S[i+k] 和 S[j+k] 与 S[i] 和 S[j] 等价,所以都用前者代替)
1、S[i+k]>S[j+k]
2、S[i+k]<S[j+k]
3、S[i+k]==S[j+k]
然后有以下操作:
/
1、S[i+k] > S[j+k] -> i=i+k+1; //i+=k+1;
2、S[i+k] < S[j+k] -> j=j+k+1; //j+=k+1;
3、S[i+k] == S[j+k] -> k++;
/
至于为什么是i=i+k+1.
举个例子 abczabcd
此时 i=0,j=4, s[i]=a,s[j]=a, 进行k++来判俩字符串字典序大小,当k=3时,s[i+k]=z > s[j+k]=d;
此时当 i 取 i 到 i+k-1 都不可能为最优解(即以i为开始位置输出整个字符串),因为不论 i 往后取一位还是两位(小于k),j也可以往后取一位或两位,例如 i 取了 bcz 那总有 j 取 bcd 比前者更优。
由于 i=i+k+1 和 j=j+k+1 执行完后可能 i == j,但两者不能相等,所以得判断一次是否相等然后 i++; 或者 j++;
这样反复操作直到遍历完整个字符串后, i 和 j 中靠左的是最优解的起点位置

AC代码

复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
#include <bits/stdc++.h> using namespace std; int num[1000000]; int n; int Min_print() { int i=0,j=1,k=0; //数组从1开始存则int i=1,j=2; while(i<n&&j<n&&k<n) { // int temp=num[(i+k-1)%(n+1)+1]-num[(j+k-1)%(n+1)+1]; //如果数组从1到n存数组,则这样取余数来循环比较,并把while循环条件改成i<=n&&j<=n&&k<=n; int temp=num[(i+k)%n]-num[(j+k)%n]; //数组从0到n-1存数就选这个;因为要循环比较整个字符串,所以 mod n来回到字符串开头来循环比较 if(!temp)k++; //如果选取的两段数前缀相同,则k++来比较下一位 else { if(temp>0)i+=k+1; else j+=k+1; if(i==j)i++; //i和j不能相同,相同则i往右移一位; k=0; } } return i<j?i:j; // 返回i和j间小的值 } signed main() { cin>>n; for(int i=0;i<n;i++) cin>>num[i]; int ans=Min_print(); bool flag=0; for(int i=0;i<n;i++)//循环输出 { if(flag)cout<<" "; flag=1; cout<<num[(i+ans)%n]; } cout<<endl; }

本文作者:Minza

本文链接:https://www.cnblogs.com/minz-io/p/17266680.html

posted @   Minza  阅读(47)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开