[WFOI]翻转序列

题目

传送门 to luogu

吐槽:洛谷月赛搞这个构造 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 2x,否则数字下标的奇偶性无法改变。

然而最后的空位很少时,数字可能最初到 i i i 的距离就在 x x x 内,比如恰好在 i + x − 1 i+x-1 i+x1 的位置。这时候,我们要让它落到 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+11n i ⩽ n − 3 x − 1 2 i\leqslant n-\frac{3x-1}{2} in23x1 。于是最后会留下 n − i = 3 x − 1 2 n-i=\frac{3x-1}{2} ni=23x1 长度的无法操作区间。怎么办?

显然我们会希望有一种方案,能够在不变动已经排好的位置的同时,将两个相邻数字交换。先分析一下 ( x − 1 ) (x-1) (x1) ( 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) (L1) 步。

如果两次操作不是同一个:先翻转 [ 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) (x1) 步,然后交换位置。先 [ 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) (x1) 步之后换位置,再连续操作相邻长度为 ( x − 1 ) (x-1) (x1) 区间使得其后退 ( x − 2 ) (x-2) (x2) 步。很可惜 L L L 不能取 x x x 啊;这只能做到让两个下标差为 2 2 2 的数字交换位置。

所以,如果最后剩余的数字,需要移动的距离都是偶数,就可以完成目标。于是看看哪个操作是可以改变奇偶性的:两个数字同时往前移动 ( L − 1 ) (L-1) (L1) 步就可以,因为 2 ∣ L 2\mid L 2L 。那么我们就可以让两个相邻数字的所需步数奇偶性同时变化。

而总步数是 ∑ ∣ p i − i ∣ ≡ ∑ p i − ∑ i = 0 \sum|p_i-i|\equiv\sum p_i-\sum i=0 piipii=0,这说明一定有偶数个这样的数字。由于变化过程中会移动,我们先把它们聚集起来,然后统一绞杀即可。

怎么聚集呢?还需要注意到,这样的数字有一个特点,就是 奇数和偶数的数量相等,无论是值还是下标。道理也很简单,因为 ⟨ i ⟩ \langle i\rangle i ⟨ p i ⟩ \langle p_i\rangle pi 是相同的,所以二者中的奇数个数、偶数个数分别相等。那么 i ≡ p i i\equiv p_i ipi 对于判定无影响,剩余的两种情况数量应当相等;而这两种情况就是所需步数为奇数的数字。

于是我们就用 “交换距离为 2 2 2 数字” 的方法,就可以让所有这样的数字跑到序列最后去(毕竟奇数偶数个数相同),然后统一 “绞杀” 就行了。

最后我们分析一下 x x x 的取值。

  • 第一步,移动大约 n − 3 x 2 n-\frac{3x}{2} n23x 的数字,步数约为 ∑ 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=23xnxi2xn289x
  • 第二步,将所有 “有罪” 的数字移到序列最末,最多 ∑ 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=123x2i169x2 次移动操作,乘以 4 4 4 的代价就是实际操作次数 9 x 2 4 {9x^2\over 4} 49x2
  • 第三步,统一 “绞杀”。最多花费 3 x 2 \frac{3x}{2} 23x 次操作。
  • 第四步,重新排序。因为 “绞杀” 时,会往前移动 ( x − 2 ) (x-2) (x2) 步,所以现在有 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} 2xn289x+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 17x2x2n2+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;
}

猜你喜欢

转载自blog.csdn.net/qq_42101694/article/details/122446891