「Gym103069C」Random Shuffle

题目

点这里看题目。

分析

关键观察在于,这道题的 \(n\)​ 居然有一个较大的下界!!!正常题目 \(n\)​ 的最小值一般都是个位数,这道题样例中 \(n=50\)​??说明这道题思路必然是通过 \(a\)​ 得到关于 \(x\)​​ 的若干位的限制,然后暴力枚举检验。这样才能解释 \(n\)​ 为什么无法取到较小的值。

接着可以手玩发现,我们实际上可以反推出一次 rand 之前的 \(x\)​。进一步地,我们考察若干次 rand 后的 \(x\)​ 和初始 \(x\)​ 的关系——其实就是一个线性变换的关系,并且这个变换必然是可逆的。所以,只要我们能够精准地给出某次 rand\(x\)​ 的若干位,我们就能得到相应的方程。

如何精确地给出 \(x\)​ 的一位?我们可以通过 \(a\)​ 得到第 \(k\)rand 过后 \(x\bmod k\) 的结果。注意到如果 \(k=2^cr,2\nmid r\),则我们也可以得到 \(x\bmod 2^c\) 的结果,进而得到 \(x\) 的低 \(c\) 位。当 \(n=50\) 的时候,我们至多可以得到 \(\sum_{k\ge 1}\lfloor\frac{n}{2^k}\rfloor=47\) 位,这样只需要枚举 \(17\) 位。

如何证明这 \(47\)​ 条方程线性无关?把前 \(50\)​​ 个矩阵拉出来验证一下就好了。另外,这个矩阵实际上一定会出现幂次循环。不过当它会出现循环的时候,可以想象指数已经相当大了,我们已经得到了足够的信息来准确地给出 \(x\)

这里动态地加入方程,其实不需要最后一块解,可以动态维护消元之后的结果,再使用 unsigned long long 优化一下即可。

小结:

  1. 对于数据范围的观察,很多时候都能得到意料之外的启发!

  2. 动态维护方程组的小技巧,记录一下;动态维护可以避免之后一块算。

    不演了,这就是消元法,只不过变量少从而限制了线性无关的方程少而已。

代码

#include <cstdio>
#include <cassert>
#include <iostream>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

typedef unsigned long long ull;

const int MAXN = 1e5 + 5, BIT = 64;

template<typename _T>
void read( _T &x ) {
	x = 0; char s = getchar(); int f = 1;
	while( ! ( '0' <= s && s <= '9' ) ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
	x *= f;
}

template<typename _T>
void write( _T x ) {
	if( x < 0 ) putchar( '-' ), x = -x;
	if( 9 < x ) write( x / 10 );
	putchar( x % 10 + '0' );
}

struct Equation {
	ull vec; bool res;

	Equation(): vec( 0 ), res( 0 ) {}
	Equation( ull V, bool R ): vec( V ), res( R ) {}
};

ull fre;

Equation equ[64];

ull mat[MAXN][64];

int perm[MAXN], pos[MAXN];;
int A[MAXN];
int N;

namespace Validator {
	ull seed;

	int tmp[MAXN];

	inline ull Rand() {
		seed ^= seed << 13;
		seed ^= seed >> 7;
		seed ^= seed << 17;
		return seed;
	}
	
	inline bool Validate( const ull &X ) {
		seed = X;
		rep( i, 1, N ) {
			tmp[i] = i;
			std :: swap( tmp[i], tmp[Rand() % i + 1] );
		}
		rep( i, 1, N )
			if( tmp[i] != A[i] )
				return false;
		return true;
	}
}

inline Equation operator ^ ( const Equation &a, const Equation &b ) {
	return Equation( a.vec ^ b.vec, a.res ^ b.res );
}

inline Equation& operator ^= ( Equation &a, const Equation &b ) {
	return a = a ^ b;
}

inline void AddEquation( Equation nw ) {
	rep( i, 0, BIT - 1 ) 
		if( nw.vec >> i & 1 ) {
			if( ! equ[i].vec ) {
				equ[i] = nw;
				break;
			}
			nw ^= equ[i];
		}
}

inline ull Generate( const ull &freState ) {
	ull ret = freState;
	rep( k, 0, BIT - 1 ) if( equ[k].vec ) {
		int idx = __builtin_ctzll( equ[k].vec );
		ret ^= ( 1llu * ( __builtin_parityll( equ[k].vec & freState ) ^ equ[k].res ) ) << idx;
	}
	return ret;
}

int main() {
	read( N );
	rep( i, 1, N ) {
		read( A[i] );
		perm[i] = A[i], pos[perm[i]] = i;
	}
	rep( i, 0, BIT - 1 ) mat[0][i] = 1llu << i;
	rep( i, 1, N )
		rep( j, 0, BIT - 1 ) {
			mat[i][j] = mat[i - 1][j];
			mat[i][j] ^= mat[i][j] << 13;
			mat[i][j] ^= mat[i][j] >> 7;
			mat[i][j] ^= mat[i][j] << 17;
		}
	per( i, N, 1 ) {
		int r = pos[i] - 1;
		std :: swap( pos[i], pos[perm[i]] );
		std :: swap( perm[pos[perm[i]]], perm[i] );
		int t = __builtin_ctz( i );
		rep( j, 0, t - 1 ) {
			Equation tmp;
			tmp.res = r >> j & 1;
			rep( k, 0, BIT - 1 )
				tmp.vec |= ( mat[i][k] >> j & 1 ) << k;
			AddEquation( tmp );
		}
	}
	fre = BIT == 64 ? -1 : ( 1llu << BIT ) - 1;
	rep( k, 0, BIT - 1 ) if( equ[k].vec ) {
		int idx = __builtin_ctzll( equ[k].vec );
		fre ^= 1llu << idx;
		rep( j, 0, BIT - 1 )
			if( j ^ k && equ[j].vec >> idx & 1 )
				equ[j] ^= equ[k];
	}
	bool ever = false; ull ans;
	for( ull s = fre ; s ; s = ( s - 1 ) & fre ) {
		ans = Generate( s );
		if( Validator :: Validate( ans ) ) {
			ever = true; break;
		}
	}
	if( ! ever ) {
		ans = Generate( 0 );
		if( Validator :: Validate( ans ) )
			ever = true;
	}
	assert( ever );
	write( ans ), putchar( '\n' );
	return 0;
}
posted @ 2022-06-22 23:15  crashed  阅读(58)  评论(0编辑  收藏  举报