[POI 2021/2022 R1] Domino 题解
前言
题目链接:洛谷。
题意简述
一个 \(2\times n\) 的矩形,上面有若干个格子被占用了。你要用 \(1\times 2\) 或 \(2\times 1\) 的牌,覆盖所有未被占用的格子,一个格子不可被占用两次。记方案数为 \(m\)。
给你 \(m\),求最小的 \(n\),使得存在一种方案设置被占用的格子,使得覆盖的方案数恰好为 \(m\),或报告无解。
\(1\leq m\leq10^{18}\)。
题目分析
妙妙题。
读懂题目后,可以尝试暴搜确定每一个格子的占用情况。考虑计算方案数。不难想到从左到右一列一列地决策,设 \(f_{i,0/1/2}\) 表示,考虑到第 \(i\) 列,前 \(i-1\) 列均非空,对于第 \(i\) 列的两行,均非空 / 上面非空 / 下面非空,非空指被占用,或者被覆盖。
- 第 \(i\) 列均未被占用。
\(f_{i,0}\gets f_{i-1,0}\):在第 \(i\) 列放置了 \(2\times 1\) 的矩形。
\(f_{i,1}\gets f_{i-1,2},f_{i,2}\gets f_{i-1,1}\):在第 \(i-1,i\) 列放置了一个 \(1\times 2\) 的矩形。
若第 \(i-1\) 列也均未被占用,\(f_{i,0}\gets f_{i,0}+f_{i-2,0}\),即放置两个 \(1\times 2\) 的矩形。 - 第 \(i\) 列上面被占用。(下面类似。)
\(f_{i,0}\gets f_{i-1,1}\):第 \(i-1,i\) 列下层放置了一个 \(1\times2\) 的矩形。第 \(i\) 列下层被覆盖,上层被占用。
\(f_{i,1}\gets f_{i-1,0}\):不放置矩形。
\(f_{i,2}\gets 0\):显然不合法。 - 第 \(i\) 列均被占用。
\(f_{i,0}\gets f_{i-1,0}\),\(f_{i,1/2}\gets 0\),进行不了操作。
以上 DP 能够做到不重不漏地统计方案。
打表后惊奇地发现,在 \(n\) 最小的前提下,对于每一种覆盖方案数为 \(m\) 的占用方案(\(m\neq0\)),占用的格子总是为若干个 \(2\times1\) 的矩形,不妨称作隔板,将 \(2\times n\) 的矩形划分成了若干个宽度大于等于一的矩形,这些被划分出来的小矩形内部格子均未被占用。
显然占用的格子数必然是偶数。假设在 \(n\) 最小的前提下,存在一种占用情况,占用的格子不满足结论。
有若干被占用的格子构成了若干隔板,剩下占用的格子至少有两个,取出其中前两个 \(C_a,C_b\),分别在第 \(i,j\) 列,讨论他们之间的位置关系。
-
他们之间被某个隔板隔开了。
隔板的左边,由于一个单独的 \(C_a\) 的存在,不存在合法的覆盖方案,方案数为 \(0\),与方案数为 \(m\neq 0\) 矛盾。 -
他们在同一行。不妨设他们在上面一行。
左边 \(2\times(i-1)\) 的矩形的所有合法覆盖方案,均不会溢出来占用第 \(i\) 列的第二行。为了填上第 \(i\) 列的第二行的空位,肯定会放置一个 \(2\times 1\) 的矩形。那么又将第 \(i+1\) 列第一行空了出来,又要用 \(2\times1\) 的矩形填上。如此,直到碰到 \(C_b\)。
若 \(j-i\) 为偶数,最终会导致第 \(j-1\) 列第一行单独空出,不能被覆盖,方案数为 \(0\),与方案数非零矛盾;否则,\(i\sim j\) 的覆盖方式存在且唯一。倘若我们将第 \(i\sim j\) 列换成一个隔板,方案数没有发生变化,而列数至少从 \(2\) 变为了 \(1\),与 \(n\) 最小矛盾。
-
他们不在同一行。
类似上面的讨论。导出要么方案数为 \(0\) 矛盾,要么与 \(n\) 最小矛盾。
故结论成立。
每一个被隔开的矩形相互独立,遵循乘法原理。对于一个 \(2\times n\) 的全空矩形,分析前文的 DP,此时只有 \(f_{i,0}\) 有意义,不难得出覆盖方案数为 \(F_n\),其中 \(\{F_n\}\) 为斐波那契数列 \(F_0=1,F_1=1,F_2=2,\ldots\)。
那么问题变为了求满足 \(\prod_{i=1}^t F_{x_i}=m\) 最小的 \(n=t-1+\sum_{i=1}^t x_i\)。由于 \(F_{87}>10^{18}\),故 \(x_i\leq86\)。显然 \(x_i\ge2\),否则由于 \(F_{x_i}=1\),去掉不改变方案数,而可以让 \(n\) 更小。
本地用 \(88.37\) 秒暴搜发现,\(10^{18}\) 以内可以被 \(F_n\) 的乘积表示出来的 \(m\) 只有 \(36338470\) 种,拆分方案数最多只有 \(70\) 种,在 \(m\in\{519400496868360192,584325558976905216\}\) 取到。这提示我们某些看起来不太正确的算法,可能实际上是正确的。
可以直接暴搜拆分方案。但是直接暴搜是错误的,参考 link 和 link。所以需要最优性剪枝,或者记忆化。
代码
#include <cstdio>
using ll = long long;
const int M = 87, inf = 2e9;
ll a[M], m;
int ans = inf;
void dfs(ll x, int c, int la) {
if (x == 1) {
if (c - 1 < ans) ans = c - 1;
return;
}
if (c + 2 >= ans) return;
for (int i = la; i >= 2; --i)
if (x % a[i] == 0)
dfs(x / a[i], c + i + 1, i);
}
signed main() {
a[0] = 1, a[1] = 1;
for (int i = 2; i < M; ++i) a[i] = a[i - 1] + a[i - 2];
scanf("%lld", &m);
if (m == 1) return puts("1"), 0;
dfs(m, 0, M - 1);
if (ans == inf) puts("NIE");
else printf("%d", ans);
return 0;
}
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18940612。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。

浙公网安备 33010602011771号