1. 程式人生 > 實用技巧 >Solution -「ARC 110F」Esoswap

Solution -「ARC 110F」Esoswap

\(\mathcal{Description}\)

  Link.

  給定 \(0\sim n-1\) 的排列 \(p_{0..n-1}\),每次操作給出 \(i\),交換 \(p_i\)\(p_{(i+p_i)\bmod n}\)。構造一種使排列升序的操作序列。

  \(n\le100\)

\(\mathcal{Solution}\)

  反正兔子就一個樣例觀察法,一個暴力偽解拍上去就 AC 了。(

  先講講我的偽解,觀察樣例解釋:

  • First, announce \(i=6\). We swap \(P_6(=5)\) and \(P_{(6+5)\bmod8}=P_3(=6)\)

    , turning \(P\) into \(7,1,2,5,4,0,6,3\).

  • Then, announce \(i=0\). We swap \(P_0(=7)\) and \(P_{(0+7)\bmod8}=P_7(=3)\), turning \(P\) into \(3,1,2,5,4,0,6,7\).

  • Then, announce \(i=3\). We swap \(P_3(=5)\) and \(P_{(3+5)\bmod8}=P_0(=3)\), turning \(P\) into \(5,1,2,3,4,0,6,7\).

  • Finally, announce \(i=0\). We swap \(P_0(=5)\)

    and \(P_{(0+5)\bmod8}=P_5(=0)\), turning \(P\) into \(0,1,2,3,4,5,6,7\).

  

  發現三次 \(p_i=5\),一次 \(p_i=7\)。考慮到樣例的迷惑性,我們嘗試讓對 \(p_i=5\) 的操作挨在一起。交換第一步和第二步,發現操作序列仍合法。

  接下來,我們強行解釋該操作序列的內在邏輯:

  • 希望 \(p_7=7\),反覆操作 \(p_i=7\) 直到 \(p_7=7\)
  • 希望 \(p_7=7\land p_6=6\),反覆操作 \(p_i=6\)(樣例中不需要操作),直到不滿足 \(p_7=7\) 回到第一步,或滿足 \(p_6=6\)
  • 希望 \(p_5=5\land p_6=6\land p_7=7\),操作同上。
  • ……

  綜上,交換策略為

選擇不滿足 \(p_i=i\) 的最大的 \(p_i\) 進行交換直到序列升序。

  然後就 AC 了,複雜度不知道。(


  正解是先操作使得 \(p=\{n-1,n-2,\cdots,0\}\) 然後逆序。操作方法考察從後往前的每個 \(i\),不斷操作 \(i\) 直至 \(p_i=n-i-1\),可證至多操作 \(\mathcal O(n)\) 次,總複雜度 \(\mathcal O(n^2)\)

\(\mathcal{Code}\)

  偽解:

/* Clearink */
 
#include <cstdio>
#include <vector>
 
#define rep( i, l, r ) for ( int i = l, rpbound##i = r; i <= rpbound##i; ++i )
#define per( i, r, l ) for ( int i = r, rpbound##i = l; i >= rpbound##i; --i )
 
const int MAXN = 100;
int n, p[MAXN + 5];
std::vector<int> ans;
 
inline void iswp ( int& a, int& b ) { a ^= b ^= a ^= b; }
 
int main () {
	scanf ( "%d", &n );
	rep ( i, 0, n - 1 ) scanf ( "%d", &p[i] );
	while ( true ) {
		int irr = -1;
		rep ( i, 0, n - 1 ) if ( i ^ p[i] && ( !~irr || p[i] > p[irr] ) ) irr = i;
		if ( !~irr ) break;
		ans.push_back ( irr );
		iswp ( p[irr], p[( irr + p[irr] ) % n] );
	}
	printf ( "%d\n", ( int ) ans.size () );
	for ( int i: ans ) printf ( "%d\n", i );
	return 0;
}

  正解:

/* Clearink */

#include <cstdio>
#include <vector>

#define rep( i, l, r ) for ( int i = l, rpbound##i = r; i <= rpbound##i; ++i )
#define per( i, r, l ) for ( int i = r, rpbound##i = l; i >= rpbound##i; --i )

const int MAXN = 100;
int n, p[MAXN + 5];
std::vector<int> ans;

inline void iswp ( int& a, int& b ) { a ^= b ^= a ^= b; }
inline void oper ( const int i ) {
	ans.push_back ( i ), iswp ( p[i], p[( i + p[i] ) % n] );
}

int main () {
	scanf ( "%d", &n );
	rep ( i, 0, n - 1 ) scanf ( "%d", &p[i] );
	per ( i, n - 1, 1 ) for ( ; p[i] != n - i - 1; oper ( i ) );
	per ( i, n - 2, 0 ) {
		rep ( j, i + 1, n - 2 ) oper ( j );
		oper ( i ), oper ( i );
	}
	printf ( "%d\n", ( int ) ans.size () );
	for ( int i: ans ) printf ( "%d\n", i );
	return 0;
}