PKU 1020 Anniversary Cake(Dfs)

一道简单的搜索题,我却做了两天,真是失败,从刚开始,没有思路,到想到方法,再到如何实现,从N次wrong后,到TLE,再剪枝优化,最后Accept,可谓一路辛苦,其中也多亏大牛的提示。 
      题意是把一个大蛋糕(边长为E),能否切成给定s(s<15)个边长为Ei(1=<Ei<=10)的小蛋糕,所有的蛋糕都是正方形.题目的规模不是很多,应该是深搜来拼,刚开始一点头绪也没有,以前只搜过点。
     1.保存状态很重要,这是遇到的第一个问题,想法是用一个矩形来存储,用一个表示列的数组d[i](这个刚开始没想到),d[i]表示第i列没填蛋糕的行数。
     2.搜索的策略,很容易想到可以从矩阵的左上角开始填,每次按列从左边向右填,左边的填完了才能天右边的,显然这样是正确的。
     3.优化与剪枝,首先,所有小蛋糕的面积和不等于要拼的蛋糕的面积,则没必要填。如果直接从左上角填这样效率很低的,可以每次按蛋糕从大到小开始填,可以想到这样很高效的。当时根据以上两点写了后,调试出正确答案后,提交依然超时,这样,如果不可以不可以拼成的话,就会执行 piece!次,看了discuss之后,才发现如果该大小的蛋糕填不进去,则其他相同大小的蛋糕就没有必要所了,回溯后,原先大小的蛋糕也没必要填,刚开始,我是用一个数组存这些切成的蛋糕的边长,然后对其递减排序,每次按数组下标填入,那么如果该大小的蛋糕放不下,则下标就应该跳rec[](表示该大小的蛋糕数),但这样不好控制,调试了很长时间都没成功。其实很简单,没必要排序,每次选最大的蛋糕,因为蛋糕最大是10,所以从10开始选,一直递减,看有没有该大小的蛋糕,能不能放入,如果放不下则选择比其小的蛋糕即可,否则,该蛋糕的数目减一,修改该矩形,搜索下一块蛋糕,如果该拼法行不通,则回溯。
这其中,反映出我很差的代码能力,以及对深度搜索认识的薄弱,以及对问题认识不够深刻,不够全面,以后这方面要努力了!加油!

 

Source Code

Problem: 1020  User: zhouxc
Memory: 200K  Time: 47MS
Language: C++  Result: Accepted

 

#include "iostream"

using namespace std;

int test,cake,piece;
int p[20],d[50],rec[20];
bool wasted,cpt;
int Find_Min()
{
       //返回最左列第一个有插完的一列的行号,以后每次会从该列插入
蛋糕,这一列前面的列都插完了,而后面的还没插
        for(int i=0;i<cake;i++)         
        {    
                if(d[i]!=0)
                    return i;
        }
        return -1;//  返回-1表示每一列都插完了,则可以正好分配
}                   
               
//  dfs切每一块蛋糕,其中cunt表示切了的蛋糕数
bool Cake_dfs(int cunt)   {
    
   // 最左边未填慢的列,下一块蛋糕将要开始从该列紧挨着已填的填入
           int rr=Find_Min();
         // printf("%d %d/n",rr,cunt);  
         if(rr==-1)     //表示正好填好
              return true;
        //每次填时尝试取最大的蛋糕         
         else                
         {      //size表示蛋糕的大小,因为蛋糕最大不超过10
               for(int size=10;size>0;size--)
               {
                 
                   if(rec[size]) //如果存在该大小的蛋糕
                   {     
                           cpt=true;  
                           //判断该大小的蛋糕是否可填入
                           for(int j=rr;j<size+rr;j++)
                           {
                               if(d[j]<size)
                               {     
                                     cpt=false;
                                     break;
                               } 
                            } 
                            if(cpt)          
                            {
                                 //把该大小的蛋糕填入,并修改填入后的d值
                                 for(int i=rr;i<size+rr;i++)
                                         d[i]-=size;
                                //该大小的蛋糕数减一
                                 rec[size]--;
                                //搜索填入下一块蛋糕
                                 if(Cake_dfs(cunt+1))
                                    return true;
                                 //当该方案行不通时回溯,尝试另一种填法  
                                 rec[size]++;
                                 for(int i=rr;i<size+rr;i++)
                                        d[i]+=size; 
                            }               
                     }
                }
                   
            } 
            return false; 
                 
}        
        
int main()
{
         scanf("%d",&test);
         while(test--)
         {
               int result=0;c=0;
               scanf("%d%d",&cake,&piece);
               memset(rec,0,sizeof(rec));
               for(int i=0;i<piece;i++)
               {
                      scanf("%d",&p);
                      result+=p*p;
                      rec[p]++;  //边长为p的蛋糕的数
               }
            //初始化,d[i]表示第i列,已填入蛋糕的行数,刚开始都没填
               for(int i=0;i<cake;i++)      
                   d[i]=cake;
                wasted=true;
              //当所有的的蛋糕的面积和等于要拼成的蛋糕面积时,开始拼,否则一定不能拼成    
               if(result==cake*cake)
                   wasted=!Cake_dfs(0);
               if(!wasted)
                     puts("KHOOOOB!");
               else
                     puts("HUTUTU!");    
         }   
         return 0;
}

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值