「THUPC2019」令人难以忘记的题目名称

题目

点这里看题目。


现在有一个长度为 \(n\) 的整数序列 \(S\)\(S\) 的元素从 \(0\) 开始编号。Alice 和 Bob 在这个序列上博弈。

博弈的过程有若干轮,每一轮的过程如下:

  • Alice 给出一个长度为 \(T\) 的整数序列。
  • Bob 选定一个非负整数 \(x\),满足 \(0\le x<n\)
  • 对于每一个 \(0\le y<n\)​​,令 \(S_y\gets S_y+T_{(y+x)\bmod n}\)​​。

如果某一轮结束后,\(S\)​ 中的数都是某一个给定质数 \(p\)​​​​ 的倍数,则说 Alice 胜利。

你需要判断 Alice 是否可以在有限多轮内保证必胜。如果可以,还需要求出最小的 \(s\)​​,使得在 \(s\)​​ 轮内 Alice 可以保证必胜。

所有数据满足 \(1\le n\le 3\times 10^5,2\le p\le 200\)​。

分析

很好玩的一道题,可惜被我草菅了。

问题看起来很复杂,先翻译一下。考虑一列命题 \(\{q_m\}\)​​​,其中 \(q_m\)​​​​:“在 \(m\)​​ 轮内 Alice 可以保证必胜”。则两个问题分别是:

  1. 是否 \(\exist m\in \mathbb N,\text{s.t. } q_m\)​。
  2. 求出 \(\min\{m\in \mathbb N|q_m\land (\neg q_{m-1}\lor m=0)\}\)​​。

我们不妨先研究一下 \(q_m\)​ 的等价命题。考虑一些比较小的情况:

  1. \(m=0\)​​ 时,这就意味着 \(S\)​ 全为 \(0\)​​。

  2. \(m=1\)​​ 时,可以发现 \(S\)​​ 包含的元素全部相同为一种可能的情况。利用反证法容易证明 \(q_1\Leftrightarrow\)​​ “\(S\)​​ 中的数全部相同”。

  3. \(m=2\)​ 时,我们需要更清晰地描述这个问题。

    不妨假设一个 \(T\)​​,使得无论 \(x\)​ 取到何值,经过变换后 \(S\)​​ 中的数都全部相同。

    也即 \(\forall 0\le x<n,\forall 0\le y<n,S_y+T_{(y+x)\bmod n}\equiv S_{(y+1)\bmod n}+T_{(y+1+x)\bmod n}\pmod p\)​​。

    也就是 \(\forall 0\le y<n,\forall 0\le x<n,S_{(y+1)\bmod n}-S_{y}\equiv T_{(y+x)\bmod n}-T_{(y+1+x)\bmod n}\pmod p\)

    \(x,y\)​​​ 取值的任意性,我们可以得到:\(\Delta S,\Delta T\)​​ 各自的数全部相同

    Note.

    这里的 \(\Delta\) 表示“循环差分”,也即 \((\Delta S)_x=S_{(x+1)\bmod n}-S_x\)。​

    根据推理过程,这是一个必要条件,其充分性也容易证明。

  4. 归纳一下已有的结果,我们发现 $q_m(m=0,1,2)\Leftrightarrow $​​​​​“在模 \(p\)​​​ 意义下,\(\Delta^m S\)​​​​​ 中的数全为 \(0\)​​​​​“。

    推广到 \(m\in \mathbb N\),这个关系都是成立的。证明可以使用数学归纳法,限于篇幅此处略去。

现在,我们要做的就是:

  1. 判定是否 \(\exist m\in \mathbb N\)​​​,使得在模 \(p\)​​​ 意义下,\(\Delta^mS\)​​​ 中的数全为 \(0\)​​​。
  2. 求一个最小的 \(m\in\mathbb N\)​​,使得在模 \(p\)​​ 意义下,\(\Delta^mS\)​​ 中的数全为 \(0\)​​。

我们可以把 \(\Delta^m\)​​ 展开来观察一下:

\[(\Delta^m S)_x=\sum_{k=0}^m\binom{m}{k}(-1)^{m-k}S_{(x+k)\bmod n} \]

注意到模数很小,我们很快就想到了经典性质 \(\dbinom{p}{k}\equiv [k=0\lor k=p]\pmod p\)​​​。此结论可来自于 Lucas 定理,所以容易推广得到 \(\forall r\in \mathbb N^*,\dbinom{p^r}{k}\equiv[k=0\lor k=p^r]\pmod p\)​​​。​

因此,对于 \(r\in \mathbb N^*\),有 \((\Delta^{p^r}S)_x=S_{(x+p^r)\bmod n}-S_x\)。当我们需要进行判断时,我们要求 \(S_{(x+p^r)\bmod n}\equiv S_x\pmod p\)。由于 \(x\) 取值的任意性,这个条件等价于 \(S_{(x+\gcd(p^r,n))\bmod n}\equiv S_x\pmod p\)

注意到,尽管 \(p^r\) 可以很大,但是 \(\gcd(p^r,n)\le n\),是有上界的。不妨设 \(r_0\) 满足 \(p^{r_0}|n,p^{r_0+1}\nmid n\)则当 \(r\ge r_0\) 的时候,\(q_{p^{r}}\) 都等价于 \(\forall 0\le x<n,S_{(x+p^{r_0})\bmod n}\equiv S_x\pmod p\)

为了解决第一个问题,可行的方法是取一个“充分大”的 \(m\)​,并检查 \(q_m\)​ 是否成立。现在我们知道,当 \(r\rightarrow +\infty\)​ 时,\(p^r\rightarrow +\infty\),而 \(q_{p^r}\)​ 始终可以被很简单地判断,所以我们已经可以解决第一个问题了。


第二个问题相对来说要简单一点,只需要注意到 \(\Delta\) 是存在结合律的即可。这意味着 \(\Delta^m\)​ 可以等价地拆成若干个 \(\Delta^{p^r}\)​ 的“乘积”。

于是,我们马上就有了一个 \(O(np\log n\log p)\)​ 的二分做法。但实际上倍增即可去掉一个 \(\log p\) 的因子。

更进一步!在倍增过程中,如果 \(\Delta^{p^r} S\)​ 全是 \(0\)​​,我们可以导出 \(\forall 0\le x<n,S_{(x+p^r)\bmod n}\equiv S_x\pmod p\)这意味着 \(S\) 可以被收缩到 \(p^r\)​ 的大小。 于是,在倍增过程中,我们需要考虑的 \(S\) 的大小是逐渐变小的。复杂度即为 \(T(n)=T(\frac n p)+O(pn)=O(np)\)

代码

#include <cstdio>

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

const int MAXN = 3e5 + 5, MAXLOG = 25;

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

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

int pw[MAXLOG];

int A[MAXN], B[MAXN];
int N, mod;

inline bool AllZero() {
	rep( i, 0, N - 1 ) if( A[i] ) return false;
	return true;
}

int main() {
	// freopen( "name.in", "r", stdin );
	// freopen( "name.out", "w", stdout );
	Read( N ), Read( mod );
	rep( i, 0, N - 1 ) Read( A[i] ), A[i] %= mod;
	int k = 0; for( int x = N ; ! ( x % mod ) ; x /= mod, k ++ );
	pw[0] = 1; rep( i, 1, k ) pw[i] = pw[i - 1] * mod;	
	rep( i, 0, N - 1 )
		if( A[i] != A[( i + pw[k] ) % N] )
			return puts( "-1" ), 0;
	if( AllZero() ) return puts( "0" ), 0;
	int ans = 0;
	for( int i = k - 1 ; ~ i ; i -- ) {
		int n = pw[i + 1], m = pw[i];
		while( true ) {
			bool flg = true;
			rep( i, 0, n - 1 ) {
				B[i] = ( A[( i + m ) % n] - A[i] + mod ) % mod;
				flg &= B[i] == 0;
			}
			if( flg ) break;
			rep( i, 0, n - 1 ) A[i] = B[i];
			ans += m;
		}
	}
	Write( ans + 1 ), putchar( '\n' );
	return 0;
}
posted @ 2023-01-18 19:31  crashed  阅读(67)  评论(0编辑  收藏  举报