题目
吐槽:洛谷月赛搞这个构造 master \text{master} master 题目,把我心态整个搞炸了……
思路
题解中说,“顺藤摸瓜” 即可。可是,藤虽然找到了,往上一摸,却只摸到了花生……
最自然的想法是,从左往右填好数字。如果后面有很多空位,那么翻转啥的都不重要,直接将翻转视为移动末端元素 x x x 步的操作。直到最后,到 i i i 的距离在 x x x 到 2 x 2x 2x 之间时,稍微走短一点,走到 i + x i+x i+x 就行了。
B t w \rm Btw Btw,一定有 2 ∤ x 2\nmid x 2∤x,否则数字下标的奇偶性无法改变。
然而最后的空位很少时,数字可能最初到 i i i 的距离就在 x x x 内,比如恰好在 i + x − 1 i+x-1 i+x−1 的位置。这时候,我们要让它落到 i + x i+x i+x 去,就需要 i + x + x + 1 2 − 1 ⩽ n i+x+\frac{x+1}{2}-1\leqslant n i+x+2x+1−1⩽n 即 i ⩽ n − 3 x − 1 2 i\leqslant n-\frac{3x-1}{2} i⩽n−23x−1 。于是最后会留下 n − i = 3 x − 1 2 n-i=\frac{3x-1}{2} n−i=23x−1 长度的无法操作区间。怎么办?
显然我们会希望有一种方案,能够在不变动已经排好的位置的同时,将两个相邻数字交换。先分析一下 ( x − 1 ) (x-1) (x−1) 和 ( x + 1 ) (x+1) (x+1) 能拿来干什么吧。着重分析两次操作的结果——因为操作次数一定是偶数,否则有一段较长区间的顺序会被颠倒;这偶数次中,又很可能是 两两一组,组内的操作几乎是互相抵消影响的。
如果两次操作是同一个:连续翻转两个相邻的长度为 L L L 的区间,比如 [ 1 , L ] [1,L] [1,L] 和 [ 2 , L + 1 ] [2,L+1] [2,L+1],会怎么样?画图可以看到,就是让 A L , A L + 1 A_L,A_{L+1} AL,AL+1 同时往前移动了 ( L − 1 ) (L-1) (L−1) 步。
如果两次操作不是同一个:先翻转 [ 1 , x ) [1,x) [1,x),然后是 [ 1 , x + 1 ] [1,x+1] [1,x+1],效果如何?就是让 A x A_x Ax 和 A x + 1 A_{x+1} Ax+1 同时前进了 ( x − 1 ) (x-1) (x−1) 步,然后交换位置。先 [ 2 , x ] [2,x] [2,x] 再 [ 1 , x + 1 ] [1,x+1] [1,x+1] 呢?就是将 A 1 A_1 A1 和 A x + 1 A_{x+1} Ax+1 交换位置。其余情况的影响较大,我们姑且不要试图使用。
此时我们发现,有两个几乎可以完成 “交换位置” 的操作:让两个数字同时前进 ( x − 1 ) (x-1) (x−1) 步之后换位置,再连续操作相邻长度为 ( x − 1 ) (x-1) (x−1) 区间使得其后退 ( x − 2 ) (x-2) (x−2) 步。很可惜 L L L 不能取 x x x 啊;这只能做到让两个下标差为 2 2 2 的数字交换位置。
所以,如果最后剩余的数字,需要移动的距离都是偶数,就可以完成目标。于是看看哪个操作是可以改变奇偶性的:两个数字同时往前移动 ( L − 1 ) (L-1) (L−1) 步就可以,因为 2 ∣ L 2\mid L 2∣L 。那么我们就可以让两个相邻数字的所需步数奇偶性同时变化。
而总步数是 ∑ ∣ p i − i ∣ ≡ ∑ p i − ∑ i = 0 \sum|p_i-i|\equiv\sum p_i-\sum i=0 ∑∣pi−i∣≡∑pi−∑i=0,这说明一定有偶数个这样的数字。由于变化过程中会移动,我们先把它们聚集起来,然后统一绞杀即可。
怎么聚集呢?还需要注意到,这样的数字有一个特点,就是 奇数和偶数的数量相等,无论是值还是下标。道理也很简单,因为 ⟨ i ⟩ \langle i\rangle ⟨i⟩ 和 ⟨ p i ⟩ \langle p_i\rangle ⟨pi⟩ 是相同的,所以二者中的奇数个数、偶数个数分别相等。那么 i ≡ p i i\equiv p_i i≡pi 对于判定无影响,剩余的两种情况数量应当相等;而这两种情况就是所需步数为奇数的数字。
于是我们就用 “交换距离为 2 2 2 数字” 的方法,就可以让所有这样的数字跑到序列最后去(毕竟奇数偶数个数相同),然后统一 “绞杀” 就行了。
最后我们分析一下 x x x 的取值。
- 第一步,移动大约 n − 3 x 2 n-\frac{3x}{2} n−23x 的数字,步数约为 ∑ i = 3 x 2 n ⌈ i x ⌉ ≈ n 2 2 x − 9 x 8 \sum_{i=\frac{3x}{2}}^{n}\lceil\frac{i}{x}\rceil\approx\frac{n^2}{2x}-\frac{9x}{8} ∑i=23xn⌈xi⌉≈2xn2−89x 。
- 第二步,将所有 “有罪” 的数字移到序列最末,最多 ∑ i = 1 3 x 2 ⌈ i 2 ⌉ ≈ 9 x 2 16 \sum_{i=1}^{\frac{3x}{2}}\lceil{i\over 2}\rceil\approx\frac{9x^2}{16} ∑i=123x⌈2i⌉≈169x2 次移动操作,乘以 4 4 4 的代价就是实际操作次数 9 x 2 4 {9x^2\over 4} 49x2 。
- 第三步,统一 “绞杀”。最多花费 3 x 2 \frac{3x}{2} 23x 次操作。
- 第四步,重新排序。因为 “绞杀” 时,会往前移动 ( x − 2 ) (x-2) (x−2) 步,所以现在有 5 x 2 \frac{5x}{2} 25x 的数字需要慢慢放好位置了。需要 25 x 2 16 {25x^2\over 16} 1625x2 次移动操作,共 25 x 2 4 {25x^2\over 4} 425x2 次翻转。
于是我们应当求该函数的最小值点:
n 2 2 x − 9 x 8 + 9 x 2 4 + 3 x 2 + 25 x 2 4 = n 2 2 x + 17 x 2 2 + 3 x 8 \frac{n^2}{2x}-\frac{9x}{8}+\frac{9x^2}{4}+\frac{3x}{2}+\frac{25x^2}{4}=\frac{n^2}{2x}+\frac{17x^2}{2}+\frac{3x}{8} 2xn2−89x+49x2+23x+425x2=2xn2+217x2+83x
求导可知最值点是 17 x − n 2 2 x 2 + 3 8 = 0 17x-\frac{n^2}{2x^2}+\frac{3}{8}=0 17x−2x2n2+83=0,约为 17 x = n 2 2 x 2 17x=\frac{n^2}{2x^2} 17x=2x2n2 即 x = n 2 34 3 x=\sqrt[3]{n^2\over 34} x=334n2,代入上式得 n 4 3 ( 34 3 2 + 17 3 4 2 3 2 ) \sqrt[3]{n^4}\left(\frac{\sqrt[3]{34}}{2}+\frac{17\sqrt[3]{34^2}}{2}\right) 3n4(2334+2173342),可见 n n n 越大比值越大。取 n = 1 0 3 , x = 31 n=10^3,\;x=31 n=103,x=31 计算出的结果约为 24.3 k 24.3\text k 24.3k;但是上面的极限情况都无法达到,足已通过。
代码
代码看上去倒也极简单(因为思路的分析本身已经挺难的了),却让我打了对拍才找到错误……
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <cmath>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MAXN = 1005;
int a[MAXN], n;
int ans[20*MAXN], tot;
void rev(int l,int r){
ans[++ tot] = n*l+(r-1);
std::reverse(a+l,a+r+1);
}
/// @warning ( @p l + @p r ) should be odd
void flip(int l,int r,const int x){
const int mid = (l+r)>>1;
return rev(mid-(x-1)/2,mid+(x+1)/2);
}
void two_steps_swap(int v,const int x){
rev(v-x,v); // length = x+1
drep(j,v,v-2) rev(j-x+2,j); // x-1
}
int main(){
n = readint();
int x = (n < 100) ? 3 : int(round(
pow(n*n/double(34),double(1)/3.0)));
if((x^1)&1) -- x; // have to be odd
rep(i,1,n) a[i] = readint();
for(int i=1,pos; i+x+(x-1)/2<=n; ++i){
rep(j,pos=i,n) if(a[j] == i) pos = j;
while(pos > i+x) rev(pos-x,pos), pos -= x;
if(pos != i && pos != i+x){
if((pos^i^x^1)&1) // twice twist
flip(pos,i+x-1,x), pos = i+x-1;
if(pos != i+x) // haven't hit the target
flip(pos,i+x,x), pos = i+x;
}
if(pos != i) rev(i,i+x); // assert(pos == i+x);
}
const int L = n-x-(x-1)/2;
if(x == 3){
// easist way
rep(i,1,n) rep(j,1,n-1) // Bubble Sort
if(a[j] > a[j+1]) rev(j,j+1);
goto SCHEME; // just output the answer
}
rep(j,L+1,n) rep(i,L+1,n-2) // Bubble Sort
if((a[i]^i)&(a[i+2]^i^1)&1)
two_steps_swap(i+2,x);
for(int i=L+1; i!=n; ++i) // execute!
if((a[i]^i)&(a[i+1]^i^1)&1)
rev(i-x,i), rev(i-x+1,i+1);
rep(j,L-x,n) rep(i,L-x,n-2) // Bubble Sort
if(a[i] > a[i+2]) two_steps_swap(i+2,x);
SCHEME:
printf("%d\n%d\n",x,tot);
rep(i,1,tot) printf("%d %d\n",ans[i]/n,ans[i]%n+1);
return 0;
}