这道题一看 n ≤ 15 n\leq 15 n≤15,状压DP无疑了……
我们可以设出一个显而易见的状态: f [ x ] [ y ] [ s ] f[x][y][s] f[x][y][s] 表示在 x × y x\times y x×y 的矩形中分成 s s s 中的每一块巧克力是否可行。
这样空间显然会炸,我们可以发现, f f f 数组中保存了太多的无用状态——有了 x x x 和 s s s, y y y 就可以唯一确定了。这样只需要用 f [ x ] [ s ] f[x][s] f[x][s] 表示在一条边长为 x x x 的矩形中分成 s s s 中的每一块巧克力是否可行即可。这样数组大小缩到了3276800,还是bool数组,稳过。
为了方便计算矩形的另一条边长,我们可以预处理出 S u m [ s ] Sum[s] Sum[s] 数组表示 S S S 中的巧克力的总面积。
方程很好列了,枚举子集 s 0 s0 s0 再判断是否能整除即可。详见代码。
另外希望大家不要忘了register与inline的重要性!论怎样不开O2卡进最优解。
C o d e : Code: Code:
#include <cstdio>
#include <cstring>
#include <cassert>
#define min(x, y) (x < y ? x : y)
int Sum[1 << 15], A[16], n, x, y, sum;
bool f[105][1 << 15], Visit[105][1 << 15], flag[1 << 15];
inline bool scan() {
sum = 0;
scanf("%d", &n);
if (n == 0) return false;
scanf("%d%d", &x, &y);
for (register int i(1); i <= n; ++ i) scanf("%d", A + i), sum += A[i];
memset(f, false, sizeof(f));
memset(flag, false, sizeof(flag));
memset(Visit, false, sizeof(Visit));
memset(Sum, 0, sizeof(Sum));
for (register int s(1); s < 1 << n; ++ s)
for (register int i(0); i < n; ++ i) if (s & 1 << i) Sum[s] += A[i + 1];
for (register int i(0); i < n; ++ i) flag[1 << i] = true;
return true;
}
inline bool dfs(int x, int s) {
if (Visit[x][s]) return f[x][s];
if (Visit[Sum[s] / x][s]) return f[Sum[s] / x][s];//能让搜索量减半的东西,不用每次取min,这里加一行就好了。
Visit[x][s] = true;
if (flag[s]) return f[x][s] = true;
register int y(Sum[s] / x);
for (register int s0(s - 1 & s); s0; s0 = s0 - 1 & s) {
if (!(Sum[s0] % x) && dfs(x, s0) && dfs(x, s ^ s0)) return f[x][s] = true;
if (!(Sum[s0] % y) && dfs(y, s0) && dfs(y, s ^ s0)) return f[x][s] = true;
}
return f[x][s];
}
int main() {
int kase(0);
while (scan())
printf("Case %d: ", ++ kase),
puts(!(sum % x) && sum == x * y && dfs(x, (1 << n) - 1) ? "Yes" : "No");
}