NKOI 3701 分享巧克力

P3701分享巧克力
时间限制 : - MS   空间限制 : 65536 KB 
评测说明 : 时限1000ms
问题描述

  给你一块长为x,宽为y的矩形巧克力。你可以对巧克力进行任意次下列操作:
  每次操作可以沿一条直线把一块巧克力切割成两块巧克力,要求切出的两块巧克力的长和宽都是整数。
  问:是否可以经过若干次上述操作,恰好得到n块面积分别为A1,A2,...,An的巧克力(巧克力要恰好用完,不能够有剩余)。如下图所示,给出你一块3*4的巧克力,我们可以将其切割成面积为1,2,3,6的四块巧克力。

  

输入格式

第一行,一个整数n,表示要求切割出的巧克力的块数。
第二行,两个整数x和y,表示初始巧克力的长和宽
第三行,n个空格间隔的整数,表示n块指定的巧克力的面积A1,A2,...,An

输出格式

一行,若能够切割成功输出"Yes",否则输出"No"

样例输入 1


3 4 
6 3 2 1 

样例输出 1

Yes

样例输入 2


2 3 
1 5

样例输出 2

No

提示

1<=n<=15
1<=x,y<=100
1<=Ai<=x*y


来源  改编自la4794

注意到n的范围很小,可以把和n有关的子集作为动态规划状态的一部分,设f[r][c][s]表示边长为r,c的巧克力是否可以切割成面积集合为s

我们可以知道,f[r][c][s]==1,当且仅当:

1.存在1<=r0<r和s的子集s0,使f[r0][c][s0]=f[r-r0][c][s-s0]=1

2.存在1<=c0<c和s的子集s0,使f[r][c0][s0]=f[r][c-c0][s-s0]=1

再稍微加一些优化就能过了

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=16,maxm=105;
int f[1<<maxn][maxm],a[maxn],sum[1<<maxn];//sum表示集合中所有元素的和
bool mark[1<<maxn][maxm];
int bitcount(int x){return x==0?0:bitcount(x>>1)+(x&1);}//算出s的二进制位中1的个数
int dp(int s,int x){
	if(mark[s][x])return f[s][x];
	mark[s][x]=1;
	if(bitcount(s)==1)return f[s][x]=1;
	int s0,s1,y=sum[s]/x;
	for(s0=(s-1)&s;s0;s0=(s0-1)&s){
		s1=s^s0;
		if(sum[s0]%x==0&&dp(s0,min(x,sum[s0]/x))&&dp(s1,min(x,sum[s1]/x)))
		    return f[s][x]=1;
		if(sum[s0]%y==0&&dp(s0,min(y,sum[s0]/y))&&dp(s1,min(y,sum[s1]/y)))
		    return f[s][x]=1;
	}
	return f[s][x]=0;
}
int main(){
	int i,x,y,ans,s,n;
	scanf("%d%d%d",&n,&x,&y);
	for(i=0;i<n;i++)scanf("%d",&a[i]);
	int all=(1<<n)-1;
	for(s=0;s<=all;s++)
	    for(i=0;i<n;i++)
	        if(s&(1<<i))sum[s]+=a[i];//求每个子集中元素的和
	if(sum[all]%x!=0||sum[all]!=x*y)ans=0;//特判
	else ans=dp(all,min(x,y));
	printf("%s",ans?"Yes":"No");
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值