最近想起来了这个问题,记得当时做《算法竞速进阶指南》见过。
不过现在忘记的差不多了,打算重新推一次。
假设当前最优的是1开头,然后我们用从2到n枚举以i开头的表示和当前最优的哪个更优。
假设现在匹配了j个字符,第j+1个不同,分两种情况讨论:
- 当前更优,那么更新
- 原来更优,i应该增加k+1,因为后面的那一部分不如前面的优。也就是 ∀ t ∈ [ 0 , k ] , S i + t > S a n s + t \forall t\in[0,k],S_{i+t}>S_{ans+t} ∀t∈[0,k],Si+t>Sans+t。
特别地,如果n个字符相等,那么就出现了循环节,直接退出即可。
这样写了个代码
#include <bits/stdc++.h>
using namespace std;
const int N = 6e5 + 10;
int n, a[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[n + i] = a[i];
}
int ans = 1;
for (int i = 2; i <= n; i++) {
int j = 0;
while (j < n && a[ans + j] == a[i + j]) {
j++;
}
if (j == n) {
break;
}
if (a[ans + j] < a[i + j]) {
i += j;
}
else {
ans = i;
}
}
for (int i = 1; i <= n; i++) {
cout << a[ans + i - 1] << " \n"[i == n];
}
}
后面仔细分析了一下发现复杂度不对,主要是更新那个地方.可以构造数据。
#include <bits/stdc++.h>
using namespace std;
int main() {
freopen("in.in", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n = 299998;
cout << n << endl;
for (int i = 1; i <= 99999; i++) {
cout << "1 2 3 ";
}
cout << "0\n";
}
卡掉。
后面想着ans和i应该也有相同的性质,如果比不过就应该直接跳,这样就想到了双指针的做法,基本和书上一样了。
#include <bits/stdc++.h>
using namespace std;
const int N = 6e5 + 10;
int n, a[N];
int main() {
// freopen("in.in", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[n + i] = a[i];
}
int i = 1, j = 2, k;
while (i <= n && j <= n) {
for (k = 0; k < n && a[i + k] == a[j + k]; k++);
if (k == n) {
break;
}
if (a[i + k] < a[j + k]) {
j += k + 1;
}
else {
i += k + 1;
}
if (i == j) {
i++;
}
}
int pos = min(i, j);
for (int i = 1; i <= n; i++) {
cout << a[pos + i - 1] << " \n"[i == n];
}
}