Project Euler 275
Project Euler 275
题目
Balanced Sculptures
Let us define a balanced sculpture of order n as follows:
- A polyomino made up of \(n+1\) tiles known as the blocks (\(n\) tiles) and the plinth (remaining tile);
- the plinth has its centre at position \((x=0, y=0)\);
- the blocks have \(y\)-coordinates greater than zero (so the plinth is the unique lowest tile);
- the centre of mass of all the blocks, combined, has \(x\)-coordinate equal to zero.
When counting the sculptures, any arrangements which are simply reflections about the \(y\)-axis, are not counted as distinct. For example, the \(18\) balanced sculptures of order \(6\) are shown below; note that each pair of mirror images (about the \(y\)-axis) is counted as one sculpture:

There are \(964\) balanced sculptures of order \(10\) and \(360505\) of order \(15\). How many balanced sculptures are there of order \(18\)?
解决方案
由于因为每个方格质量相同,所以质心的 \(x\) 坐标等于 blocks 的 \(x\) 坐标平均值为 \(0\)。此外,关于 \(y\) 轴的镜像视为同一个,这启发我们可以镜像地进行操作拼接。
本题基本思路是搜索,但是使用了如下思想进行剪枝:
Redelmeier 思想
朴素做法是:每次在形状边界上加一个格子,靠“形状哈希/判重”去掉重复。但 polyomino 的分支数非常大,判重也很贵。Redelmeier 方法的关键是:通过“候选格子的编号顺序 + 单调选择规则”,从构造过程上保证每个 fixed polyomino 只生成一次,从而避免大规模判重。
具体来说,我们维护两个集合概念:
- 当前形状(已放入的格子)。
frontier(候选边界列表):所有与当前形状边相邻、且尚未加入形状、也合法的空格子。frontier的插入顺序就是它的“编号”。
那么递归步骤如下:
- 选择一个
frontier中的格子加入形状; - 把它的四邻域(上下左右)中符合规则且未出现过的格子加入
frontier(会得到更大的编号); - 下一次递归时,只允许选择“编号不小于本次所选编号”的候选格(也就是从
frontier的某个起点往后选)。
这条“编号单调”规则就是 Redelmeier 的去重核心:同一个形状可能有很多加入顺序,但只有一种顺序会满足“每一步都从允许的编号区间中挑选”,因此不会重复生成。
镜像去重
如果先生成所有形状再做镜像归并,会多一倍左右的工作;更好的做法是把“镜像不区分”变成搜索约束。
维护一个状态:当前部分形状是否仍关于 \(y\) 轴对称(记为
symmetric)。
若
symmetric为真:当考虑加入一个候选格 \((x,y)\) 且 \(x\neq 0\) 时,它的镜像 \((-x,y)\) 也会同时出现在候选集中(可以视作“成对候选”)。
此时分两条分支:
- 保持对称:一次性把这对格子都加入(消耗 \(2\) 个 blocks),
symmetric仍为真; - 打破对称:只加入其中一侧(消耗 \(1\) 个 block),然后进入
symmetric为假的状态。
- 保持对称:一次性把这对格子都加入(消耗 \(2\) 个 blocks),
关键点:只允许在“成对候选”的一个代表侧执行“打破对称”,这样不会生成镜像等价的重复分支。
若
symmetric为假: 对称已经破坏,不再需要特殊处理(正常枚举即可),最终计数时也不再需要“镜像归并”。
这种策略的本质是:把“镜像等价类”中的一个代表在搜索树里唯一化,从源头避免重复。
平衡条件与剪枝
若一直保持对称:每个 \(x\neq 0\)
的格子都成对出现、互相抵消,且 \(x=0\)
的格子贡献为 \(0\),因此 blocks 的
\(\sum x\) 必为 \(0\),无需再算。 只有当
symmetric 被打破后,才需要维护:
当已经不对称时,仍需放置 \(r\) 个 blocks。设当前 blocks 的 \(x\) 取值范围大致落在 \([x_{\min}, x_{\max}]\)。
由于 polyomino 是通过“贴边加格”生长的,每放一个格子,极端情况下 \(x_{\min}\) 最多向左扩 \(1\)、\(x_{\max}\) 最多向右扩 \(1\)。于是剩余 \(r\) 步中:
- 最“偏左”的 \(x\) 贡献可以粗略下界为:\(r\cdot x_{\min} - \dfrac{r(r+1)}{2}\)
- 最“偏右”的贡献上界为:\(r\cdot x_{\max} + \dfrac{r(r+1)}{2}\)
如果把下一步要加的格子的 \(x\) 也计入(得到新和 \(S'\)),那么最终可能达到的总和范围必落在:
\[ \left[S' + rx_{\min} - \dfrac{r(r+1)}{2}, S' + r x_{\max} + \dfrac{r(r+1)}{2}\right] \]
代码
1 |
|