POJ 1020 Anniversary Cake(dfs+分割思想)【转】

21 篇文章 0 订阅

 

                                                             转自:YoU  http://blog.csdn.net/lyy289065406/article/details/6683250

Anniversary Cake

问题描述 :

Nahid Khaleh decides to invite the kids of the “Shahr-e Ghashang” to her wedding anniversary. She wants to prepare a square-shaped chocolate cake with known size. She asks each invited person to determine the size of the piece of cake that he/she wants (which should also be square-shaped). She knows that Mr. Kavoosi would not bear any wasting of the cake. She wants to know whether she can make a square cake with that size that serves everybody exactly with the requested size, and without any waste.

输入:

The first line of the input file contains a single integer t (1 ≤ t ≤ 10), the number of test cases, followed by input data for each test case. Each test case consist of a single line containing an integer s, the side of the cake, followed by an integer n (1 ≤ n ≤ 16), the number of cake pieces, followed by n integers (in the range 1..10) specifying the side of each piece.

输出:

There should be one output line per test case containing one of the words KHOOOOB! or HUTUTU! depending on whether the cake can be cut into pieces of specified size without any waste or not.

样例输入:

2
4 8 1 1 1 1 1 3 1 1
5 6 3 3 2 1 1 1

样例输出:

KHOOOOB!
HUTUTU!

大致题意:

有一块边长为BoxSize的正方形的大蛋糕,现在给出n块不同尺寸的正方形的小蛋糕的边长,问是否能把大蛋糕按恰好切割为这n块小蛋糕,要求每块小蛋糕必须为整块。

 

解题思路:

有技巧的DFS

 

可以把大蛋糕想象为一个蛋糕盒子,然后往里面装小蛋糕。

装蛋糕时遵循以下原则:

自下而上,自左至右;

即先装好盒子底部,再继续往上层装,且装每一层时都靠左边放蛋糕;

大蛋糕优先装,因为小蛋糕灵活度比较高。

 

只要把问题变换为上述问题,我想对深搜比较熟悉的同学也会马上得到思路了,这个只是很简单的DFS思路。

 

但是本题的难点不在于怎样去DFS,而是每放入一个蛋糕后,怎样去标记盒子已经放有蛋糕的位置?

我初始的做这题时,因为看到数据规模不大(Max_n=16,Max_size=10,那么大蛋糕最大也就40*40),于是我把尺寸为BoxSize的盒子划分为BoxSize*BoxSize个1*1的格子,每放入一个大小为size的蛋糕,就用一个二重循环去标记size*size的格子。

最后是毫无悬念地TLE了。

看了别人的方法,发现或分格子的思路是正确的,但应该“按列标记”。不但把盒子看做多个1*1个格子,也把小蛋糕看做多个1*1的单位,建立一个一维数组col[ BoxSize ],每放入一个蛋糕,则去记录每列的格子被填充的数目。

例如在第2~4列放入了一个size=3的小蛋糕,那么col[2]+=3, col[3]+=3, col[4]+=3。有同学会问,为什么行不用计数?要是放入蛋糕后,该蛋糕底部出现部分悬空怎么处理?这个情况是不会出现的,因为当前DFS遵循先把底部放满原则,要是出现悬空,则会回溯。

代码:

//Memory Time 
//208K  32MS 

#include<iostream>
using namespace std;

int BoxSize;      //盒子尺寸
int n;            //蛋糕的总个数
int SizeNum[11];  //各种尺寸的蛋糕个数
int col[41];      //把盒子纵行分割成BoxSize*BoxSize个1*1大小的小格子
                  //col[i]记录第i列被填充了的格子数

bool DFS(int FillNum)   //FillNum:已放入盒子的蛋糕数
{
	if(FillNum==n)
		return true;

	/*寻找格子数被填充最少的列,靠左优先*/
	int min=50;
	int prow;
	for(int i=1;i<=BoxSize;i++)
		if(min>col[i])
		{
			min=col[i];
			prow=i;
		}

	/*枚举各种尺寸的蛋糕自下而上地放入盒子*/
    for(int size=10;size>=1;size--)
	{
		if(!SizeNum[size])
			continue;

		//检查尺寸为size的蛋糕放入盒子时在纵向和横向是否越界
		if(BoxSize-col[prow]>=size && BoxSize-prow+1>=size)
        {
			//检查盒子从第prow列到第prow+size-1列,共size列的宽度wide中
			//是否每列剩余的空间都足够放入高度为size的蛋糕
            int wide=0;
            for(int r=prow;r<=prow+size-1;r++)
			{
				if(col[r]<=col[prow])  //比较各列的"填充数"
				{    //注意,这里若比较"未填充数"BoxSize-col[r]<size会TLE
					wide++;       //虽然两个条件等价,但确实计算了3秒左右,不知何故
					continue;
				}
				break;
			}

            if(wide>=size)
            {
				int r;
                //放入尺寸为size的蛋糕
				SizeNum[size]--;
				for(r=prow;r<=prow+size-1;r++)
					col[r]+=size;
			
				if(DFS(FillNum+1))
					return true;
 
				//回溯
				SizeNum[size]++;
				for(r=prow;r<=prow+size-1;r++)
					col[r]-=size;
            }
        }
	}
    return false;
}

int main(void)
{
	int test;
	cin>>test;
	for(int t=1;t<=test;t++)
	{
		memset(SizeNum,0,sizeof(SizeNum));
		memset(col,0,sizeof(col));

		cin>>BoxSize>>n;

		int cnt=0;   //记录size>(BoxSize/2)的蛋糕个数
		int area=0;  //计算所有蛋糕的面积之和
		for(int i=1;i<=n;i++)
		{
			int size;
			cin>>size;
			area+=size*size;
			SizeNum[size]++;

			if(size>BoxSize/2)
				cnt++;
		}

		if(cnt>1 || area!=BoxSize*BoxSize)
		{
			cout<<"HUTUTU!"<<endl;
			continue;
		}

		if(DFS(0))
			cout<<"KHOOOOB!"<<endl;
		else
			cout<<"HUTUTU!"<<endl;
	}
	return 0;
}

ps:

因为自己写的严重tle。。所以就百度了,这个博主写的很好也易懂。。很懒的我就直接转载了


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值