Project Euler 331

Project Euler 331

题目

Cross flips

\(N\times N\) disks are placed on a square game board. Each disk has a black side and white side.

At each turn, you may choose a disk and flip all the disks in the same row and the same column as this disk: thus \(2\times N-1\) disks are flipped. The game ends when all disks show their white side. The following example shows a game on a \(5\times5\) board.

It can be proven that \(3\) is the minimal number of turns to finish this game.

The bottom left disk on the \(N\times N\) board has coordinates \((0,0)\);

the bottom right disk has coordinates \((N-1,0)\) and the top left disk has coordinates \((0,N-1)\).

Let \(C_N\) be the following configuration of a board with \(N\times N\) disks:

A disk at \((x,y)\) satisfying \(N-1 \le \sqrt{x^2+y^2} \lt N\), shows its black side; otherwise, it shows its white side. \(C_5\) is shown above.

Let \(T(N)\) be the minimal number of turns to finish a game starting from configuration \(C_N\) or \(0\) if configuration \(C_N\) is unsolvable.

We have shown that \(T(5)=3\). You are also given that \(T(10)=29\) and \(T(1 000)=395253\).

Find \(\sum \limits_{i = 3}^{31} {T(2^i - i)}\).

解决方案

把每个格子的颜色用 \(0/1\) 表示(白 \(0\),黑 \(1\)),所有格子组成向量空间 \(\mathbb F_2^{N^2}\)

\(X_{ij}\in\{0,1\}\) 表示是否在 \((i,j)\) 按一次(按两次等于没按,所以只需 \(0/1\))。设初始棋盘为矩阵 \(B\),操作集合为矩阵 \(X\),最终棋盘为 \(B+\Phi(X)\)(加法在 \(\mathbb F_2\) 中即异或)。要清空棋盘等价于\(\Phi(X)=B.\)

关键是写出线性算子 \(\Phi\)。按一次 \((i,j)\) 会翻转整行 \(i\) 与整列 \(j\),在 \(\mathbb F_2\) 下“翻转”就是加 \(1\)。因此对任意 \(X\),令 \[R_i=\sum_{k=0}^{N-1}X_{ik}\bmod 2, C_j=\sum_{k=0}^{N-1}X_{kj}\bmod 2.\] 那么 \((i,j)\) 这个格子被翻转的次数,正是“本格子是否被按”加上“本行按的个数”加上“本列按的个数”(交点在行与列里被算两次,在 \(\mathbb F_2\) 中自动抵消,最终仍留下 \(X_{ij}\) 这一项)。于是得到非常核心的公式: \[(\Phi(X))_{ij}=(X_{ij}+R_i+C_j)\bmod 2 \tag{1}\]

接下来研究算子平方 \(\Phi(\Phi(X))\)。令 \(Y=\Phi(X)\),其行列和为\(\displaystyle{R'_i=\sum_{j=0}^{N-1} Y_{ij} \bmod 2, C'_j=\sum_{i=0}^{N-1} Y_{ij} \bmod 2.}\)

\((1)\)

\[ R'_i=\sum_j\left(X_{ij}+R_i+C_j\right)=\underbrace{\sum_j X_{ij}}_{R_i}+\underbrace{\sum_j R_i}_{N R_i}+\underbrace{\sum_j C_j}_{S}, \]

其中\(\displaystyle{S=\sum_{i,j}X_{ij}\bmod 2}\)\(X\)\(1\) 的总个数的奇偶性。于是\(R'_i=((1+N)R_i+S)\bmod 2.\)同理 \(C'_j=((1+N)C_j+S)\bmod 2.\)

再套一次 \((1)\)

\[ (\Phi^2(X))_{ij} =Y_{ij}+R'_i+C'_j =\bigl(X_{ij}+R_i+C_j\bigr)+R'_i+C'_j. \]

\(N\) 偶数,则 \(1+N\equiv 1\pmod 2\),所以\(R'_i=R_i+S, C'_j=C_j+S.\)代回去:\((\Phi^2(X))_{ij}=X_{ij}+R_i+C_j+(R_i+S)+(C_j+S)=X_{ij}.\)

因此,当\(N\)为偶数时,有

\[\Phi^2=\mathrm{Id} \tag{2}\]

其中\(\mathrm{Id}\)是指恒等映射。因此可直接推出:\(\Phi\) 可逆且 \(\Phi^{-1}=\Phi\)。所以方程 \(\Phi(X)=B\) 的唯一解是

\[X=\Phi(B) \tag{3}\]

这点非常强:偶数阶时解不仅存在且唯一,而且就是把同一个算子再作用回去。

\(N\) 奇数,则 \(1+N\equiv 0\pmod 2\),于是\(R'_i=S, C'_j=S.\)代回去:\((\Phi^2(X))_{ij}=X_{ij}+R_i+C_j+S+S=X_{ij}+R_i+C_j=(\Phi(X))_{ij}.\)

所以,当\(N\)为奇数时,有

\[\Phi^2=\Phi\tag{4}\]

这就是“奇数阶出现不可解约束”的根源:\(\Phi\) 不再是可逆算子,而是一个投影(idempotent)。于是对任何可达局面 \(B=\Phi(X)\),必满足 \(\Phi(B)=\Phi(\Phi(X))=\Phi(X)=B.\)也就是说,奇数阶可解的充要条件为:

\[\Phi(B)=B \tag{5}\]

\((1)\) 套在 \(B\) 上,令\(\displaystyle{r_i=\sum_j B_{ij}\bmod 2, c_j=\sum_i B_{ij}\bmod 2,}\)\((\Phi(B))_{ij}=B_{ij}+r_i+c_j.\)因此 \(\Phi(B)=B\) 等价于对所有 \(i,j\) 都有\(r_i+c_j=0\bmod 2.\)也就是所有行奇偶必须相同、所有列奇偶也必须相同,并且两者相同:

\[\exists t\in\{0,1\}\text{s.t.}\forall i,r_i=t;\forall j,c_j=t \tag{6}\]

这就是奇数阶的可解的必要条件——它是由 \((4)\) 导致的代数必然性。

回到本题的棋盘\(C_N\)。本题初始 \(B\) 为圆环格点:\((N-1)^2\le x^2+y^2 < N^2.\)

先看最底行 \(y=0\):黑格满足\((N-1)^2\le x^2 < N^2.\)因为 \(0\le x\le N-1\),唯一满足的是 \(x=N-1\),所以底行黑格数为 \(1\)(奇数)。因此若 \(N\) 为奇数且可解,根据 \((6)\) 必须取 \(t=1\),也就是每一行都必须有奇数个黑格。

现在取题目真正会遇到的奇数尺寸:\(N=2^i-i\),其中\(i\)是奇数。

考虑最顶行 \(y=N-1\):黑格条件变为\((N-1)^2\le x^2+(N-1)^2 < N^2\iff x^2 < N^2-(N-1)^2=2N-1.\)

所以顶行黑格个数是

\[ b_{\text{top}}=\left|{x\in\mathbb Z:0\le x\le N-1,x^2<2N-1}\right|=\left\lceil\sqrt{2N-1}\right\rceil \tag{7} \]

\(N=2^i-i\) 代入:\(2N-1=2^{i+1}-2i-1.\)\(a=2^{\frac{i+1}{2}}, a^2=2^{i+1}\),这是因为\(i\)是奇数,需要保持指数为整数。则 \(2N-1=a^2-(2i+1)<a^2 \Rightarrow\sqrt{2N-1}<a \Rightarrow b_{\text{top}}\le a.\)

再证明 \(\sqrt{2N-1}>a-1\)(从而天花板恰为 \(a\))。只需证\((a-1)^2<2N-1.\)展开:\((a-1)^2=a^2-2a+1=2^{i+1}-2^{\frac{i+3}{2}}+1.\)因此 \((a-1)^2<2N-1\) 等价于\(2^{\frac{i+3}{2}}>2i+2.\)

\(i\ge 5\) 时,左边指数增长、右边线性增长,且基例 \(i=5\)\(2^{\frac{8}{2}}=16>12,\)所以对所有奇数 \(i\ge 5\) 成立。于是 \((a-1)^2<2N-1<a^2 \Rightarrow\left\lceil\sqrt{2N-1}\right\rceil=a.\)

结合 \((7)\),得到结论:\(i\ge 5\)且为奇数时,\(b_{\text{top}}=2^{\frac{i+1}{2}}\)为偶数但底行黑格数为 \(1\)(奇数),因此“所有行都奇数”被顶行直接打破,故不可解

结论:

  • 对题目范围 \(i=3,5,7,\dots,31\)(奇数):

    • \(i=3\)\(N=5\):可解,题目给出 \(T(5)=3\)
    • 所有 \(i\ge 5\):由上述结论的顶行偶数黑格,必不可解,故 \(T(N)=0\)

偶数阶时由 \((3)\) 唯一解为 \(X=\Phi(B)\)。写成分量形式:令 \(B\) 的行列奇偶为\(\displaystyle{r_i=\sum_j B_{ij}\bmod 2, c_j=\sum_i B_{ij}\bmod 2,}\)则由 \((1)\)得:\(X_{ij}=(B_{ij}+r_i+c_j)\bmod 2.\)这意味着:

  • \(r_i=c_j\),则 \(X_{ij}=B_{ij}\)
  • \(r_i\ne c_j\),则 \(X_{ij}=1-B_{ij}\)

最少步数就是 \(X\)\(1\) 的个数。将其拆开可以整理成非常实用的形式。记\(s=\#\{i:r_i=1\}, t=\#\{j:c_j=1\}.\)

则满足 \(r_i\ne c_j\) 的格子总数为\(s(N-t)+(N-s)t.\)再把黑格的“加一/减一”修正并入,可写为

\[ \boxed{T(N)=s(N-t)+(N-s)t+\sum_{(x,y)\in\mathcal B}\bigl(1-2\cdot \mathbf{1}\{r_x\ne c_y\}\bigr)} \tag{9} \]

其中 \(\mathcal B\) 为黑格集合,\(\mathbf{1}\{\cdot\}\) 为指示函数。

本题圆环满足 \(B_{xy}=B_{yx}\),故每行黑格数等于对应列黑格数,从而\(r_i=c_i, s=t.\)于是 \((9)\) 简化为

\[ \boxed{T(N)=2s(N-s)+\sum_{(x,y)\in\mathcal B}\begin{cases} +1,& r_x=r_y,\\ -1,& r_x\ne r_y. \end{cases}} \tag{10} \]

所以偶数 \(N\) 的任务变成两件事:

  1. 统计 \(s\):有多少个行(等价列)黑格数为奇数;
  2. 统计黑格上的符号和:每个黑格看 \(r_x\)\(r_y\) 是否相等,等则 \(+1\),不等则 \(-1\)

如图所示,是\(C_{2^6-6}\)的棋盘:

定义区间\(I=[(N-1)^2,N^2).\)任意黑格 \((x,y)\) 满足 \(x^2+y^2\in I\)。注意到 \(I\) 的长度为\(|I|=N^2-(N-1)^2=2N-1.\)

若某个黑格同时存在左邻 \((x-1,y)\) 与上邻 \((x,y+1)\) 也为黑格,则两者对应的平方半径之差为\((x^2+(y+1)^2)-((x-1)^2+y^2)=2(x+y).\)

但这两者都落在区间 \(I\) 中,故差值必须小于 \(|I|\),即\(2(x+y)\le 2N-2\Rightarrow x+y\le N-1.\)

另一方面,由 \(x^2+y^2\ge (N-1)^2\)\(x,y\ge 0\)\((x+y)^2\ge x^2+y^2\ge (N-1)^2\Rightarrow x+y\ge N-1.\)

合并得 \(x+y=N-1\)。但当 \(x+y=N-1\) 时,\(x^2+y^2\le (x+y)^2=(N-1)^2,\)要达到 \(\ge (N-1)^2\) 只能取等号,而等号仅在 \(xy=0\) 时成立,即只能是端点 \((0,N-1)\)\((N-1,0)\)。在端点处显然不可能同时有“左邻”和“上邻”都在棋盘内成为黑格。

因此得到结论:对任意非端点黑格 \((x,y)\),左邻与上邻不可能同时为黑格。所以沿着“能走就向左,否则能走就向上,否则走对角”的规则,路径是确定的,并且会恰好遍历每一个黑格一次。

这使得可以用一次遍历完成所有统计。

遍历顺序从 \((N-1,0)\) 走到 \((0,N-1)\),每步只可能是

  • 上:\((x,y)\to(x-1,y)\)
  • 左:\((x,y)\to(x,y+1)\)
  • 对角线:\((x,y)\to(x-1,y+1)\)

在遍历过程中:

  • “当前行 \(y\) 的黑格个数奇偶”用变量维护(每遇到一个黑格异或 \(1\));
  • “当前列 \(x\) 的黑格个数奇偶”用变量维护。

当发生向上或者是对角线方向时时,意味着当前行结束,此时行奇偶就是该行的 \(r_y\),于是可以把它计入 \(s\)

难点在于计算\(\displaystyle{\sum_{(x,y)\in\mathcal B}(\pm 1)}\)。其中符号取决于该黑格所在行与列的最终奇偶(即 \(r_x,r_y\))。在遍历过程中,有时一边的奇偶已确定、另一边还没确定,需要“暂存贡献”。但由于路径结构的特殊性:

  • 当向上走(结束一行)时,除了当前格子所在的那一列外,该行中其它黑格所在的列都已经被完全走完(列奇偶已知);
  • 当向左走(结束一列)时,除了当前格子所在的那一行外,该列中其它黑格所在的行都已经被完全走完(行奇偶已知)。

因此只需要常数个“待结算计数”即可:

  • 当前行中,列奇偶已知的黑格数,按“列奇偶为 \(0/1\)”分类;
  • 当前列中,行奇偶已知的黑格数,按“行奇偶为 \(0/1\)”分类。

当一行结束时(得到 \(r_y\)),可以立刻结算\(\text{same}-\text{diff}\)(同奇偶贡献 \(+1\),不同奇偶贡献 \(-1\)),并清空该行的待结算计数。当一列结束时(得到对应的列奇偶),同理结算并清空该列的待结算计数。

最后用 \((10)\)

\[T(N)=2s(N-s)+\text{corr}.\]

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#include <bits/stdc++.h>
using namespace std;
const int N = 30;
typedef long long ll;
/*
仅处理偶数 n 的情况(本题总和里奇数 n 只有 n=5 贡献 3,其余奇数 n 不可解贡献 0)。

背景(只写必要结论):
- 在 GF(2) 线性化后,偶数 n 时变换算子满足 Phi^2 = Id,因此解唯一,且解的按键矩阵 X = Phi(B)。
- 最少步数等于 X 中 1 的个数。
- 对本题圆环初始图案 B(关于主对角线对称),最少步数可写成:
T(n) = 2*s*(n-s) + corr
其中
s = 行(等价列)黑格数为奇数的条数,
corr = 对所有黑格 (x,y) 计入 +1 或 -1 的修正项(取决于该行/列奇偶是否相同)。
- 关键在于无需建 O(n) 数组求行列奇偶:圆环黑格在第一象限构成一条“无分叉”的边界,
可以沿边界走一遍,用 O(1) 状态把 s 与 corr 聚合出来。
*/

ll T_even(ll n)
{
/*
我们只在第一象限上三角区域(x <= y)行走,并利用对称性将全盘贡献折算为聚合公式。
行走路径可理解为:沿圆环在第一象限的“内外边界之间”的黑格带的外侧边缘走,
从 (0, n-1) 一路走到对角线附近。

坐标约定:
- x:从 0 向右增
- y:从 n-1 向下减(因为我们扫描上三角的“行”可以视为 y 递减)
*/

ll x = 0;
ll y = n - 1;

/*
r 维护:
r = x^2 + y^2 - (n-1)^2
解释:
- (n-1)^2 是内圆半径平方;当 r < 0 表示点落入内圆以内(不在圆环中)。
- 外圆约束是 x^2 + y^2 < n^2。
之所以用 r:可以用增量 O(1) 更新而不用 sqrt。
初始点 (0, n-1) 正好在内圆上:r = 0。
*/
ll r = 0;

/*
n21 = 2*(n-1)
用于外圆判定的等价不等式:
(x+1)^2 + y^2 < n^2
结合 r 的定义可化成:
r + 2x < 2*(n-1)
(这是把 n^2 与 (n-1)^2 的差 (2n-1) 以及增量 (2x+1) 整理后的结果,
实现中用严格不等号并做了等价变形)
*/
ll n21 = 2 * (n - 1);

/*
cnt:当前固定 y 下,在上三角区域内这一“行”上连续黑格段的长度。
我们在同一行里尝试不断右移 (x++) 扩展黑格段,直到将越过外圆,此时转入下一行 (y--)。
*/
ll cnt = 1;

/*
odd:最终等于 s(奇数行/列的数量)。
为什么只用一个 odd:
- 本题图案关于对角线对称,所以“第 i 行奇偶” == “第 i 列奇偶”,计数可共用。
*/
ll odd = 0;

/*
was / newwas:用于记录“相邻两行在同一列交界处是否形成两层相邻黑格”的事件。
直观上:
- 当从上一行结束转到下一行时,黑格段的右端点可能与上一行的右端点对齐或相邻,
从而导致某一列在这两行中各有一个黑格(列黑格数为 2),这会影响该列奇偶。
- 这里把这种“列里出现 2 个黑格”的情况抽象为 double 事件(记作 1),否则 0。
was:上一行末端的 double 标记
newwas:这一行末端的 double 标记
both = was + newwas:用于批量结算“列奇偶已知/未知”的折算差异(见下方公式)
*/
ll was = 0;

/*
corr:修正项累计值。
最终答案为:
T(n) = 2*odd*(n-odd) + corr
corr 的来源是:
- 对每个黑格,根据“该行奇偶 == 该列奇偶”记 +1,否则记 -1
- 但不显式存每行每列奇偶时,可在沿边界扫描时把贡献批量累加到 corr
*/
ll corr = 0;

/*
主循环:保持在上三角区域 x < y 里行走。
当 x==y 时到达对角线,退出主循环并做收尾。
*/
while (x < y)
{
/*
尝试在同一行右移到 (x+1, y):
若 (x+1, y) 仍在外圆内,则黑格段继续延伸,cnt++。
判定用增量形式:r + 2x < 2*(n-1)
*/
if (r + 2 * x < n21)
{
/*
x -> x+1 时,r 增量为:
(x+1)^2 - x^2 = 2x+1
用两步写成 r += x; ++x; r += x; 等价于 r += 2x+1
好处是避免乘法、保持整数安全(且和下面 y 的更新风格一致)。
*/
r += x;
++x;
r += x;
++cnt; // 当前行的黑格段长度 +1
}
else
{
/*
不能继续右移,则结束这一行的黑格段,转到下一行 y -> y-1。
y -> y-1 时,r 增量为:
(y-1)^2 - y^2 = - (2y-1)
同样用两步写成 r -= y; --y; r -= y; 等价于 r -= (2y+1_old) 但注意 y 已改变。
*/
r -= y;
--y;
r -= y;

/*
newwas 决定这一行末端是否形成 double。
经验上,在圆环边界上从一行切到下一行时:
- 若此时 r < 0,说明点落入内圆,需要做一次“对角补偿” x->x+1 拉回圆环带,
此时 newwas = 0(表示没有形成两层相邻黑格的那种列结构)。
- 若 r >= 0,则 newwas = 1(表示形成了该 double 结构)。
这套判定对应边界走法的几何性质(无须 sqrt)。
*/
ll newwas;
if (r < 0)
{
// 对角补偿:x -> x+1,r 增量同上为 2x+1
r += x;
++x;
r += x;
newwas = 0;
}
else
{
newwas = 1;
}

/*
both = was + newwas 取值为 0/1/2,用于批量结算:
- 行奇偶(由 cnt 决定)
- 与列奇偶交互产生的 corr 修正
- odd(奇数行/列计数)累加
*/
ll both = was + newwas;

/*
delta = cnt - 2*both:
- cnt 是当前行黑格段长度
- both 反映两端是否发生 double,double 会导致“在折算到行/列奇偶时”少算/多算 1,
因而在聚合中表现为对 cnt 的线性修正。
*/
ll delta = cnt - 2 * both;

/*
corr 批量更新:
直观理解:
- 当 cnt 为奇数时,该行最终奇偶为 1,对应一类符号加法;
- 当 cnt 为偶数时,该行最终奇偶为 0,对应另一类符号加法;
这里把两种情况统一成:
if (cnt odd) corr += 2*delta
else corr += -2*delta
乘以 2 是因为我们只走上三角(x<=y)的一半贡献,利用对称折算后会出现成对项。
*/
corr += (cnt & 1) ? (2 * delta) : (-2 * delta);

/*
odd(最终的 s)更新:
odd 累加的是“行/列奇偶为 1”的数量。
该聚合式来自对圆环边界结构的计数:
- cnt 给出本行的黑格数
- (cnt&1) 与 both 共同决定本行/本列奇偶的翻转次数
这里的公式:
odd += cnt + (cnt&1) - both
是把本行结束时对“奇偶为 1”的条数贡献一次性结算。
*/
odd += cnt + (cnt & 1) - both;

/*
准备进入下一行:
- was 变成当前行末端标记,供下一次切行使用
- cnt 重置为 1,从新行的第一个黑格开始数
*/
was = newwas;
cnt = 1;
}
}

/*
收尾:处理对角线位置 x==y 的最后一个单元(或最后一段)。
到了对角线时,上三角边界扫描结束,但还差最后一行/列的聚合结算。
*/
if (x == y)
{
/*
odd 的最后修正:
- 对角线处的结构会让“是否发生 double(was)”影响最后一次奇偶计数
*/
odd += 1 - was;

/*
corr 的最后修正:
- 若 cnt>1,说明对角附近这一行末尾还包含长度为 cnt 的黑格段,需要按 was 调整
- 否则就是单点情形
该分支对应边界在对角线附近的两种几何落点。
*/
if (cnt > 1)
corr += 4 * was - 1;
else
corr += 1;
}

/*
主项:2*odd*(n-odd)
- odd = s(奇数行/列数量)
- 在偶数 n 情况下,最少步数的主贡献是:
2*s*(n-s)
(来自“行列奇偶不同”的格子数)
加上 corr(来自黑格上的 ±1 修正)得到最终 T(n)。
*/
return corr + 2 * odd * (n - odd);
}

int main()
{
/*
总和:
sum_{i=3}^{31} T(2^i - i)

其中:
- i=3 -> n=5,题面给出 T(5)=3
- i 为奇数且 >=5 时,n 为奇数且本题圆环图案不可解,T=0
- 仅需计算 i 为偶数:4,6,...,30(此时 n 为偶数,且可用 T_even)
*/
ll total = 3;

for (int i = 4; i <= N; i += 2)
{
ll n = (1 << i) - i;
total += T_even(n);
}

cout << total << "\n";
return 0;
}