F 牛牛与交换排序
此题首先我们可以得知,对于给定的序列, k k k 一定是固定的(已经排好序的除外)。这里的 k k k 一定等于 e − s + 1 e - s + 1 e−s+1 (s,e见下方代码)。用反证法即可证明,主要的证明条件是翻转区间长度固定且每次只能翻转左端点比上次翻转的左端点更大的区间。
for (int i = 1; i <= n; ++i) {
if (a[i] != i) {
s = i;
goal = i;
break;
}
}
for (int i = s + 1; i <= n; ++i) {
if (a[i] == s) {
e = i;
break;
}
}
证得了上面的k一定固定后,那么首先这题的暴力的解法就出来了。从第一个错位的元素开始(即上述代码中的s),每次判断区间的左右端点哪个端点会等于目标元素。如果都不等则不可能成功。如果右端点等于目标元素,则将此区间反转。然后依次往后遍历,最后再看无法作为左端点的一些点是否同样排好序即可。那么显然这是一个 O ( n 2 ) O(n^2) O(n2) 的解法,我们需要想办法优化这种解法。
这里可以优化的地方就是翻转区间,使用deque可以将一次翻转区间的时间复杂度降到 O ( 1 ) O(1) O(1) 。现在我们枚举每个需要判断的右端点(每个可能要翻转的区间的右端点)。
for (int i = e; i <= n; ++i) //枚举每个需要判断的区间的右端点
假设对于 i i i 号右端点而言,我们都可以得知当前前 k − 1 k - 1 k−1 个元素的状态,那么将右端点插在其尾部,就一定可以得到 i i i号区间整个区间的状态,且可以更新下一个区间的前 k − 1 k - 1 k−1 个状态。那么此时用deque就可以实现这个想法,我们用deque保存当前前 k − 1 k - 1 k−1 个元素,设置一个变量 t t t 表示当前deque中应当顺着看还是倒着看。那么当我们遍历到第 i i i 号右端点时,如果deque中的序列是要顺着看的,那么此时右端点理应插在deque尾部,即此时序列的真正的尾部,如果deque中的序列是倒序的,那么真正的尾部理应在deque的头部,那么右端点 a [ i ] a[i] a[i] 应插在deque头部。这样 i i i号区间就更新成功。然后判断左右端点哪个是目标元素,如果是右边就pop_back, t t t 置为倒序状态,pop_back的理由是因为我们要保证deque中是区间的前 k − 1 k-1 k−1 个元素。那么如果是左端点就pop_front,然后 t t t 设为顺序就好。这样就实现了对于 i i i 号右端点而言,假设我们可以得知当前前 k − 1 k - 1 k−1 个元素的状态,就一定可以通过此方式得到 i i i号区间整个区间的状态,且更新下一个区间的前 k − 1 k - 1 k−1 个元素 的状态。所以我们只要知道第一个区间的前 k − 1 k - 1 k−1 个元素的状态,就可以不断往后 O ( 1 ) O(1) O(1) 更新
代码实现
#include <iostream>
#include <cstdio>
#include <deque>
using namespace std;
int n, s, e, t = 1, goal;
int a[100005];
deque<int> de;
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for (int i = 1; i <= n; ++i) {
if (a[i] != i) {
s = i;
goal = i;
break;
}
}
if (!s) {
printf("yes\n1");
return 0;
}
for (int i = s + 1; i <= n; ++i) {
if (a[i] == s) {
e = i;
break;
}
}
for (int i = s; i < e; ++i) {
de.push_back(a[i]);
}
for (int i = e; i <= n; ++i) {
if (t) de.push_back(a[i]);
else de.push_front(a[i]);
if (de.back() == goal) {
de.pop_back();
t = 0;
}
else if (de.front() == goal) {
de.pop_front();
t = 1;
}
else {
printf("no");
return 0;
}
goal++;
}
if (t) {
for (auto i : de) {
if (i != goal++) {
printf("no");
return 0;
}
}
}
else {
for (auto i : de) {
if (i != n--) {
printf("no");
return 0;
}
}
}
printf("yes\n%d", e - s + 1);
}