给出一块长为x, 宽为y的矩形巧克力, 每次操作可以沿一条直线把一块巧克力切割成两块长宽均为整数的巧克力(一次不能同时切割多块巧克力) 。问: 是否可以经过若干次操作得到n块面积分别为a1,a2,…, an的巧克力。 如图1-44所示, 可以把3×4的巧克力切成面积分别为6,3, 2,1的4块。
图 1-44
【输入格式】
输入包含若干组数据。 每组数据的第一行为一个整数n(1≤n≤15) ;第二行为两个整数x和y(1≤x,y≤100) ; 第三行为n个整数a1,a2,…,an。 输入结束标志为n=0。
【输出格式】
对于每组数据, 如果可以切割成功, 输出“Yes”, 否则输出“No”。
【分析】
注意到n的规模很小, 可以把与n有关的子集作为动态规划状态的一部分。 设f(r,c, S) 表示r行c列的巧克力是否可以切割成面积集合S。 样例1的答案为Yes, 即f(3,4, {6,3, 2,1} ) =1。 第一刀把巧克力切成了3×3和3×1两块, 即f(3,3, {6,2, 1} ) =f(3,1, {3} ) =1。
不难得到下面的状态转移规则: f(r,c, S) =1当且仅当
❑ 存在1≤r0<r和S的子集S0, 使得f(r0,c, S0) =f(r-r0,c, S-S0) =1, 或者
❑ 存在1≤c0<c和S的子集S0, 使得f(r,c0,S0) =f(r,c-c0,S-S0) =1。
前者对应横着切, 后者对应竖着切。 状态有O(xy2n) 个, 每个状态转移到O(x+y) 个状态, 总时间复杂度为O((x+y)xy2n) , 有些偏大。
其实, 上述状态有些浪费。 如果r×c不等于S中所有元素之和(记为sum(S) ) , 显然f(r,c, S) =0。 换句话说, 我们可以只计算r*c=sum(S) 的状态f(r,c, S) 。 另外,f(r,c, S) =f(c,r, S) , 所以不妨设r≤c, 然后用g(r,S) 代替f(r,c, S) 。 这样, 状态降为了O(x2n) 个。 在枚举决策时, 一旦确定了S0, 实际上可以计算出r0或者c0(或者发现不存在这样的r0或者c0) , 因此总决策数为O(x3n) , 这也是本算法的时间复杂度。 由于很多状态达不到, 推荐用记忆化搜索实现,实际运算量往往远小于O(x3n) 。
最后有一点需要注意, 输入之后需要比较所有ai之和是否为x×y(想一想, 为什么) 。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 16, M = 100+5;
int a[N], sum[1<<N], d[1<<N][M], vis[1<<N][M];
int bitCount(int x){
return x == 0 ? 0 : bitCount(x>>1) + (x&1);
}
int dp(int s,int x){
if(vis[s][x]) return d[s][x];
vis[s][x] = 1;
int& ans = d[s][x];
if(bitCount(s) == 1) return ans = 1;
int y = sum[s] / x;
for(int s0 = (s-1)&s; s0; s0 = (s0-1)&s){//枚举s的子集
int s1 = s - s0;
if(sum[s0]%x == 0 && dp(s0,min(x,sum[s0]/x)) && dp(s1, min(x,sum[s1]/x))) return ans = 1;
if(sum[s0]%y == 0 && dp(s0,min(y,sum[s0]/y)) && dp(s1, min(y,sum[s1]/y))) return ans = 1;
}
return ans = 0;
}
int main(int argc, char** argv) {
int kase = 0, n;
while( ~scanf("%d",&n) && n){
int x, y;
scanf("%d%d",&x,&y);
for(int i = 0; i < n; i++)
scanf("%d",&a[i]);
memset(sum,0,sizeof(sum)); // 每个子集中的元素之和
for(int i = 0, len = 1 << n; i < len; i++)
for(int j = 0; j < n; j++)
if((1<<j) & i) sum[i] += a[j];
int all = (1<<n) - 1;
memset(vis,0,sizeof(vis));
int ans;
if(sum[all] != x*y || sum[all]%x != 0) ans = 0;
else ans = dp(all, min(x,y));
printf("Case %d: %s\n", ++kase, ans ? "Yes" : "No");
}
return 0;
}