题目链接:
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3540
题目大意:
给一块长x,宽y的巧克力,和一个数组A={a1, a2, …,an},问能否经过若干次切分后,得到面积分别为a1,a2,…an的n块巧克力。每次切分只可以选择一块巧克力,将其分为两半,如下图,3×4的巧克力经过切分后,可以得到面积分别为6,3,2,1的巧克力。
解题思路:
假设能得到n块小巧克力,考虑切分的过程,第一次切分后巧克力被分为两部分,最终结果中的任一快巧克力a[i]要么来自第一部分,要么来自第二部分,即两部分分别对应一个A的子集。那么枚举A的子集A0,另A1=A-A0,如果能找到当前巧克力的一种切分方式,让第一部分能分成A0对应的小巧克力,第二部分分成A1对应的小巧克力,则找到了一组合法的解。
定义dp状态如下,dp[x][S](S是二进制表示的集合)表示边长分别为x, S对应面积/x的巧克力能否切分成S对应集合,若能则为1,否则为0。考虑到边长x*y=面积,因此只保留一个边长,另一边可以求出来。
此代码中枚举子集的方法是数位dp的一个技巧。
参考代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 #define N 16 6 7 bool f[105][1<<N]; 8 bool vis[105][1<<N]; 9 int A[N], sum[1<<N]; 10 int cntbit(int x) 11 { 12 int ret = 0; 13 while(x) ret += x&1, x >>= 1; 14 return ret; 15 } 16 17 bool dp(int x, int cur)//cur用二进制表示当前集合 18 { 19 if(vis[x][cur] == 1) return f[x][cur]; 20 vis[x][cur] = 1; 21 bool &ans = f[x][cur]; 22 int y = sum[cur]/x; 23 if(cntbit(cur) == 1) 24 { 25 vis[x][cur] = 1; 26 return ans = true; 27 } 28 for(int s0 = (cur-1)&cur; s0; s0 = (s0-1) & cur)//枚举子集的方法 29 { 30 int s1 = cur-s0; 31 if(sum[s0]%x == 0 && dp(min(x, sum[s0]/x), s0) && dp(min(x, sum[s1]/x), s1)) 32 return ans = 1; 33 if(sum[s0]%y == 0 && dp(min(y, sum[s0]/y), s0) && dp(min(y, sum[s1]/y), s1)) 34 return ans = 1; 35 } 36 return ans = 0; 37 } 38 39 int main() 40 { 41 int n, x, y, cas = 1; 42 while(~scanf("%d", &n), n) 43 { 44 scanf("%d %d", &x, &y); 45 for(int i = 0; i < n; i++) scanf("%d", &A[i]); 46 47 memset(sum, 0, sizeof(sum)); 48 for(int i = 0; i < (1<<n); i++) 49 for(int j = 0; j < n; j++) if(i&(1<<j)) sum[i] += A[j]; 50 51 int d = (1<<n)-1; 52 if(sum[d] != x*y) 53 { 54 printf("Case %d: No ", cas++); 55 continue; 56 } 57 58 memset(vis, 0, sizeof(vis)); 59 bool ans = dp(min(x, y), d); 60 printf("Case %d: ", cas++); 61 puts(ans ? "Yes" : "No"); 62 } 63 return 0; 64 }