[蓝桥杯][算法提高VIP]Sharing Chocolate(状压dp&&记忆化搜索)

题目描述
每天,巧克力在它的许多形式上被全世界数百万人分享。它是一个真正普遍的糖果,实际上在世界上每个国家都能得到。
你发现唯一比吃巧克力更好的事情是把它分享给朋友。不幸的是,你的朋友非常挑剔,有着不同的胃口:有的喜欢让你提供较多的巧克力,而其他的喜欢让你提供较少的巧克力。你发现当他们的要求可以相互叠加时,这个事情就变得越来越难决断。现在是写一个程序来一次性完全解决这个问题的时间!
你的巧克力是矩形的。巧克力由同样大小的矩形块组成。你可以沿着巧克力中行或者列的分割线将巧克力分成两块来分享你的巧克力。你可以重复地用同样手段将分成的小块继续分割。你的每个朋友坚持要得到巧克力中的一个矩形部分,这个部分包含一个确定地小块数。你也有些坚持心:如果这块巧克力能全部分给你的朋友,不剩下任何部分,你才会分割你的巧克力。
例如图9表示将一个由3×4个小块组成巧克力块分割3次,分成各自包含6、3、2、1个小块的4部分的一种方法。(这相当于输入样例中第一个测试数据。)
在这里插入图片描述
输入
输入数据包含多组测试数据,每组测试数据描述一个要分享的巧克力块。每组测试数据的第一行包含一个整数n(1<=n<=15),表示巧克力需要分割成的块数。第二行包含两个整数x、y,表示巧克力块的两个方向上的长度。第三行包含n个正整数,表示n个部分各自需要包含的小块数。
输入数据终止于只包含整数0的一行。
输出
对于每组测试数据,先输出它的测试点编号。然后输出将巧克力按照指定的方法分割是否有可能:如果可能,输出“Yes”,否则输出“No”。按照输出样例中的格式输出。
样例输入
4
3 4
6 3 2 1
2
2 3
1 5
0
样例输出
Case 1: Yes
Case 2: No
第一次做这种难度的状压dp。简单的说一下自己的理解吧,因为有一些东西我也没有理解透,参考着看一下吧。
思路:
第一眼看到题目,很多人肯定想的是,怎么用给定的这些巧克力将r * c的巧克力表示出来。但是这些巧克力长和宽都不一样,怎么表示?很难。那么我们就转换一下思路,不去表示r*c的巧克力,而是怎么用r * c的巧克力,一下一下的切,从而得到这些小的巧克力。一整块大的巧克力,只能横着切或者竖着切,例如一块r * c的巧克力,横着切的话,分别得到((r-r0) * c)和(r0 * c)的两块巧克力。竖着切也是一样的。
并且,切出来的长方形可以任意组合的,比如要切出两块4的和2的巧克力,你不用管它怎么组合,怎么拼在一起的,它肯定是由一块6的巧克力切出来的。
接下来就说说怎么实现代码了。
所谓的状压,这个题目,n很小,那么我们就从n下手。我们把(0~(1<<n)-1)这些状态所代表的巧克力大小都计算出来。例如10101,就代表的是第1,3,5块巧克力所代表的巧克力和。最终的状态是S(11111),它是由x * y的巧克力来形成的。现在我们知道了总面积S,那么我们再知道一条边,就可以表示出另外一条边了,这样的话,就可以缩小一维,降低时间复杂度。
记忆化搜索代码如下:

inline int check(int s)//这一部分,类似于求二进制中1的个数,也就是求巧克力的块数。
{
	if(s==0) return 0;
	else return check(s>>1)+(s&1);
}
inline int dfs(int s,int x)
{
	if(dp[s][x]!=-1) return dp[s][x];
	if(check(s)==1) return dp[s][x]=1;//如果还剩下一块巧克力,这块巧克力一定可以拼出这个形状。
	int y=sum[s]/x;
	for(int s0=(s-1)&s;s0;s0=(s0-1)&s)//这里自己模拟一下吧,利用&运算分离出s中的1.
	{
		int s1=s-s0;//这里最好自己模拟一下,位运算的神秘之处,很巧妙的就可以表达出所有的状态。
		if(sum[s0]%x==0&&dfs(s0,min(x,sum[s0]/x))&&dfs(s1,min(x,sum[s1]/x))) return dp[s][x]=1;//横着切,分成上下两部分,只有这上下两部分都符合,这个状态才符合。
		if(sum[s0]%y==0&&dfs(s0,min(y,sum[s0]/y))&&dfs(s1,min(y,sum[s1]/y))) return dp[s][x]=1;//竖着切,分成左右两部分,只有左右两部分都符合,这个状态才符合。
	}
	return dp[s][x]=0;
}

整体代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int maxx=1<<17;
int dp[maxx][107],sum[maxx],a[maxx];
int n;

inline int check(int s)
{
	if(s==0) return 0;
	else return check(s>>1)+(s&1);
}
inline int dfs(int s,int x)
{
	if(dp[s][x]!=-1) return dp[s][x];
	if(check(s)==1) return dp[s][x]=1;
	int y=sum[s]/x;
	for(int s0=(s-1)&s;s0;s0=(s0-1)&s)
	{
		int s1=s-s0;
		if(sum[s0]%x==0&&dfs(s0,min(x,sum[s0]/x))&&dfs(s1,min(x,sum[s1]/x))) return dp[s][x]=1;
		if(sum[s0]%y==0&&dfs(s0,min(y,sum[s0]/y))&&dfs(s1,min(y,sum[s1]/y))) return dp[s][x]=1;
	}
	return dp[s][x]=0;
}
int main()
{
	int k=0;
	while(scanf("%d",&n),n)
	{
		memset(sum,0,sizeof(sum));
		memset(dp,-1,sizeof(dp));
		int x,y;
		scanf("%d%d",&x,&y);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		for(int s=0;s<(1<<n);s++)//枚举所有可能的状态,然后这个状态下,所有位置为1的,就代表有这一块巧克力,那么就加上这一块巧克力的大小。
			for(int i=1;i<=n;i++) if(s&(1<<(i-1))) sum[s]+=a[i];
		int all=(1<<n)-1;
		int flag=0;
		if(sum[all]!=x*y||sum[all]%min(x,y)) flag=0;
		else flag=dfs(all,min(x,y));
		printf("Case %d: %s\n",++k,flag==1?"Yes":"No");
	}
	return 0;
}

很好的一道题目,思维量很大。
努力加油a啊,(o)/~

发布了652 篇原创文章 · 获赞 101 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/starlet_kiss/article/details/105397086