UVa 1009 Sharing Chocolate (数位dp)

时间:2025-03-03 18:36:02

题目链接:

  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的巧克力。

UVa 1009 Sharing Chocolate (数位dp)

解题思路:

  假设能得到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的一个技巧。

参考代码:

 #include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 16 bool f[][<<N];
bool vis[][<<N];
int A[N], sum[<<N];
int cntbit(int x)
{
int ret = ;
while(x) ret += x&, x >>= ;
return ret;
} bool dp(int x, int cur)//cur用二进制表示当前集合
{
if(vis[x][cur] == ) return f[x][cur];
vis[x][cur] = ;
bool &ans = f[x][cur];
int y = sum[cur]/x;
if(cntbit(cur) == )
{
vis[x][cur] = ;
return ans = true;
}
for(int s0 = (cur-)&cur; s0; s0 = (s0-) & cur)//枚举子集的方法
{
int s1 = cur-s0;
if(sum[s0]%x == && dp(min(x, sum[s0]/x), s0) && dp(min(x, sum[s1]/x), s1))
return ans = ;
if(sum[s0]%y == && dp(min(y, sum[s0]/y), s0) && dp(min(y, sum[s1]/y), s1))
return ans = ;
}
return ans = ;
} int main()
{
int n, x, y, cas = ;
while(~scanf("%d", &n), n)
{
scanf("%d %d", &x, &y);
for(int i = ; i < n; i++) scanf("%d", &A[i]); memset(sum, , sizeof(sum));
for(int i = ; i < (<<n); i++)
for(int j = ; j < n; j++) if(i&(<<j)) sum[i] += A[j]; int d = (<<n)-;
if(sum[d] != x*y)
{
printf("Case %d: No\n", cas++);
continue;
} memset(vis, , sizeof(vis));
bool ans = dp(min(x, y), d);
printf("Case %d: ", cas++);
puts(ans ? "Yes" : "No");
}
return ;
}