タイトルリンク:https://acm.zcmu.edu.cn/JudgeOnline/problem.php?id = 1550
トピック
文字列sを指定すると、左に回転し、文字列を数回左にシフトした後、辞書式順序が最小になるように要求できます。
左にサイクルします。たとえば、sは(adac)で、左シフトはadac、daca、acad、cadaになります。ここでの最小の辞書式順序はacadであるため、左に3回シフトする必要があります。
アイデア
O(n ^ 2)はタイムアウトします、これがO(n)アプローチです
i、jは2つのポインタであり、2つの文字列の最初の文字の位置を示します。kは、2つの文字列の最長の共通プレフィックス長を表します。2つの配列は辞書式順序で比較する必要があるため、iとjを同じにすることはできず、iは0に初期化され、jは1に初期化され、kは0に初期化されます。
s [i + k] == s [j + k]の場合、共通の接頭辞としてのkの長さは1増加します。
s [i + k]> s [j + k]の場合、iはk + 1ジャンプして、i + k +1の位置に到達します。
s [i + k] <s [j + k]の場合、jはk +1をジャンプしてj + k + 1の位置に到達します。なぜですか?
注意すべき点は、辞書式順序が最小の文字列として、s [i + 1からj-1]で始まる同じ長さで、s [i]で始まる文字列以上でなければならないということです。 、それ以外の場合、iはずっと前に後ろにジャンプし、s [i + k]> s [j + k]の場合、jで始まる添え字の辞書式順序が小さいことを示します。
次に、iを調整する必要があります.jから始まる文字列の長さがkであるため、iの開始後のkの位置は、jから始まる現在の文字列よりも小さくならないため、実行する必要はありません。 k +1ポジションジャンプする必要がありました。s[i + k] <s [j + k]の場合、同じことが当てはまります。
次に、iとjが同じにならないようにするために、判断する必要があります。同じである場合は、j ++とし、最後に、iから始まる長さnの文字列の辞書式順序を最小にする必要があります。
ACコード
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 3e5 + 5;
char s[maxn];
int cal(int n){
int i = 0, j = 1, k = 0;
while(i < n && j < n && k < n) {
int c1 = (i + k) % n, c2 = (j + k) % n;
if(s[c1] == s[c2]) k ++;
else {
if(s[c1] < s[c2]) j += k + 1;
else i += k + 1;
if(i == j) j ++;
k = 0;
}
}
return i;
}
int main(){
int t; scanf("%d",&t);
while(t --){
scanf("%s", s);
printf("%d\n", cal(strlen(s)));
}
return 0;
}
追加の質問
別の同様の質問 P1368 [テンプレート]最小表記
メソッドはまったく同じと言えますが、辞書式順序が最小の文字列を出力します。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 3e5 + 5;
int s[maxn];
int cal(int n){
int i = 0, j = 1, k = 0;
while(i < n && j < n && k < n){
int d1 = (i + k) % n, d2 = (j + k) % n;
if(s[d1] == s[d2]) k ++;
else{
if(s[d1] > s[d2]) i += k + 1;
else j += k + 1;
if(i == j) j ++;
k = 0;
}
}
return i;
}
int main(){
int n; scanf("%d",&n);
for(int i = 0; i < n; i ++) {
scanf("%d",&s[i]);
}
int ans = cal(n);
for(int i = 0; i < n; i ++) {
if(i) printf(" ");
printf("%d", s[(i + ans) % n]);
}
puts("");
return 0;
}