剪格子

转于:http://blog.csdn.net/jopus/article/details/20619895

 

  历届试题 剪格子  

时间限制:1.0s   内存限制:256.0MB

问题描述

如下图所示,3 x 3 的格子中填写了一些整数。

+--*--+--+
|10* 1|52|
+--****--+
|20|30* 1|
*******--+
| 1| 2| 3|
+--+--+--+

我们沿着图中的星号线剪开,得到两个部分,每个部分的数字和都是60。

本题的要求就是请你编程判定:对给定的m x n 的格子中的整数,是否可以分割为两个部分,使得这两个区域的数字和相等。

如果存在多种解答,请输出包含左上角格子的那个区域包含的格子的最小数目。

如果无法分割,则输出 0。

输入格式

程序先读入两个整数 m n 用空格分割 (m,n<10)。

表示表格的宽度和高度。

接下来是n行,每行m个正整数,用空格分开。每个整数不大于10000。

输出格式

输出一个整数,表示在所有解中,包含左上角的分割区可能包含的最小的格子数目。

样例输入1

3 3
10 1 52
20 30 1
1 2 3

样例输出1

3

样例输入2

4 3
1 1 1 1
1 30 80 2
1 1 1 100

样例输出2

10

 

 

 

 

思想:(深搜+回溯) 最后实现全部遍历每种可能情况。

具体做法:从左上角开始搜索,按照   [上下左右]   的先后顺序。搜索过的点做上标记下次不再搜索(当回溯时,我们又去掉这个标记,使之下次还能被搜索)。

回溯有两种情况,第一种就是当某个点的上下左右都不满足时,回溯(退到上一步)。第二种情况就是当找到一种满足条件情况时,回溯。

其中注意,回溯时,我们需要记录当前情况剪掉的格子个数,以便于和下一次如果有多种情况比较,选出最小的剪格子数。

另外还有就是,回溯的时候,需要做3件事(即back()函数):

1:将总和(sum)减去回溯掉的点。(因为你回退了一步,所以总和肯定不能有被回溯掉的那个点了)。

2:剪掉的格子数(count)-1。(假如:当前记录了你走了3步,意思就是剪掉3个格子,当你回退一步,你就只走了两步....所以...)

3:将标记重置(flag)。(因为你下次还要访问这个点,不然你如何遍历全部?)

 

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. /* 
  2.     Name: 蓝桥杯 剪格子  
  3.     Copyright: Analyst  
  4.     Author: Analyst  
  5.     Date: 05/03/14 15:31 
  6.     Description: dev-cpp 5.5.3 
  7. */  
  8. #include <stdio.h>  
  9.   
  10. int  m, n, half=0, sum = 0, minValue = 100, count = 0;  
  11. int num[10][10]={0}, flag[10][10] = {0};  
  12. int xzb[]={-1,1,0,0};   /*上下左右*/  
  13. int yzb[]={0,0,-1,1};  
  14.   
  15. int isok(int x, int y)  /*判断传入的坐标值是否能被选入*/  
  16. {  
  17.     int yes = 0;        /*不越界并且不超出和一半*/  
  18.     if ((x >= n || x < 0) || (y >= m || y < 0) || (flag[x][y] == 1) || (sum+num[x][y] > half))  
  19.         yes = 1;    /*冲突*/     
  20.     return yes;  
  21. }  
  22.   
  23. void back(int value, int x, int y)   /*执行回退处理*/  
  24. {  
  25.     --count;  
  26.     sum -= value;  
  27.     flag[x][y] = 0;   
  28. }  
  29.   
  30. void dfs(int value, int x, int y)    /*dfs搜索*/  
  31. {  
  32.     int i, t1, t2;  
  33.       
  34.     flag[x][y] = 1;           /*标记为已走过*/  
  35.     ++count;                  /*已走过点的个数*/  
  36.     sum += value;             /*已走过的点的和*/  
  37.     if (sum == half)  
  38.     {                         /*选出剪掉点数最少的方案*/  
  39.         minValue = minValue > count ? count : minValue;  
  40.     }  
  41.     else   
  42.     {  
  43.         for (i = 0; i < 4; ++i)  /*按 上下左右 顺序遍历*/  
  44.         {  
  45.             t1 = x + xzb[i];     /*引入t1,t2目的是使传入的x,y值不变,便于下面的回退*/  
  46.             t2 = y + yzb[i];  
  47.             if (isok(t1, t2) == 1)/*不可走*/  
  48.                 continue;  
  49.             dfs(num[t1][t2], t1, t2);  
  50.         }  
  51.     }  
  52.     back(value, x, y);    /*回退处理*/  
  53. }  
  54.   
  55. int main()  
  56. {  
  57.     int i, j, maxNum = 0, all = 0;  
  58.       
  59.     scanf ("%d%d", &m, &n);   
  60.     for (i = 0; i < n; ++i)  
  61.         for (j = 0; j < m; ++j)  
  62.         {  
  63.             scanf ("%d", &num[i][j]);  
  64.                 all += num[i][j];   /*all为总和*/  
  65.             if (num[i][j] > maxNum)   /*找出最大值*/  
  66.                 maxNum = num[i][j];  
  67.         }  
  68.     if (all % 2 != 0 || maxNum > all / 2)/*如果和为基数或者最大值大于总和一半*/  
  69.         printf("0\n");  
  70.     else  
  71.     {  
  72.         half = all / 2;  
  73.         dfs(num[0][0], 0, 0);  
  74.         if (minValue != 100)       /*找到*/  
  75.             printf("%d\n",minValue);  
  76.         else   
  77.             printf("0\n");  
  78.     }  
  79.       
  80.     return 0;  
  81. }   

 

修正:感谢楼下nemoforif 的指正。

 

我们先看一组数据:

2 2

1 1

1 3

正确的搜索结果应该是1 1 1 = 3。对于这组数据,上面的算法显然存在问题。因为上面的算法是按照上下左右的方式进行搜索的。

对于1 1 1 3这组数据,必须回溯到1(0.0)才能对右边的1(0,1)进行搜索。这样就将点1(1,0)回溯掉了。使问题找不到结果。

 

将问题抽象一下:

1 1 1 3这个例子就相当于上面中间的图,由于中间有一条线(就是中间的数据都很大,又不能选入,将两边数据隔开了,导致找不到结果)

对于这种情况我想了一个其他的解决方法:

小分析一下,看上面右边的图,出现1 1 1 3这种情况,则B,C两点肯定是要选进去的,所以我们可以从B或者C再搜索一次,而这样搜索就不会存在被挡住的现象了。

方案:我们进行两次搜索,起始点分别为A , B(A,C也可以),然后再选出合适的结果。

补充代码很简单:我们在主函数里面再调用一次dfs()函数就可以了。

说明:由于第二次是从(B点)开始搜索,当找到一组结果时,我们需要判断num[0][0](左上角)的点有没有被选入。

 

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. /* 
  2.     Name: 蓝桥杯 剪格子  
  3.     Copyright: Analyst  
  4.     Author: Analyst  
  5.     Date: 05/03/14 15:31 
  6.     Description: dev-cpp 5.5.3 
  7. */  
  8. #include <stdio.h>  
  9.   
  10. int  m, n, half=0, sum = 0, minValue = 100, count = 0, select = 1;  
  11. int num[10][10]={0}, flag[10][10] = {0};  
  12. int xzb[]={-1,1,0,0};   /*上下左右*/  
  13. int yzb[]={0,0,-1,1};  
  14.   
  15. int isok(int x, int y)  /*判断传入的坐标值是否能被选入*/  
  16. {  
  17.     int yes = 0;        /*不越界并且不超出和一半*/  
  18.     if ((x >= n || x < 0) || (y >= m || y < 0) || (flag[x][y] == 1) || (sum+num[x][y] > half))  
  19.         yes = 1;    /*冲突*/     
  20.     return yes;  
  21. }  
  22.   
  23. void back(int value, int x, int y)   /*执行回退处理*/  
  24. {  
  25.     if (x == 0 && y == 0)            /*如果将num[0][0]回溯掉,则标记为未选入*/  
  26.         select = 0;  
  27.     --count;  
  28.     sum -= value;  
  29.     flag[x][y] = 0;   
  30. }  
  31.   
  32. void dfs(int value, int x, int y)    /*dfs搜索*/  
  33. {  
  34.     int i, t1, t2;  
  35.       
  36.     if (x == 0 && y == 0)     /*主要用于B点开始的搜素,判断num[0][0]有没有被选入*/  
  37.         select = 1;  
  38.     flag[x][y] = 1;           /*标记为已走过*/  
  39.     ++count;                  /*已走过点的个数*/  
  40.     sum += value;             /*已走过的点的和*/  
  41.     if (sum == half)  
  42.     {                           
  43.         if (select == 1)      /*如果num[0,0]被选入,主要用于非[0,0]点开始的搜索*/  
  44.             minValue = minValue > count ? count : minValue; /*选出剪掉点数最少的方案*/  
  45.     }  
  46.     else   
  47.     {  
  48.         for (i = 0; i < 4; ++i)  /*按 上下左右 顺序遍历*/  
  49.         {  
  50.             t1 = x + xzb[i];     /*引入t1,t2目的是使传入的x,y值不变,便于下面的回退*/  
  51.             t2 = y + yzb[i];  
  52.             if (isok(t1, t2) == 1)/*不可走*/  
  53.                 continue;  
  54.             dfs(num[t1][t2], t1, t2);  
  55.         }  
  56.     }  
  57.     back(value, x, y);           /*如果上下左右都不可走:回退*/  
  58. }  
  59.   
  60. int main()  
  61. {  
  62.     int i, j, maxNum = 0, all = 0;  
  63.       
  64.     scanf ("%d%d", &m, &n);   
  65.     for (i = 0; i < n; ++i)  
  66.         for (j = 0; j < m; ++j)  
  67.         {  
  68.             scanf ("%d", &num[i][j]);  
  69.                 all += num[i][j];   /*all为总和*/  
  70.             if (num[i][j] > maxNum)   /*找出最大值*/  
  71.                 maxNum = num[i][j];  
  72.         }  
  73.     if (all % 2 != 0 || maxNum > all / 2)/*如果和为基数或者最大值大于总和一半*/  
  74.         printf("0\n");  
  75.     else  
  76.     {  
  77.         half = all / 2;  
  78.         dfs(num[0][0], 0, 0);      /*A点开始搜索*/  
  79.         if (n > 1)                 /*行数 > 1*/  
  80.         {   
  81.             select = 0;            /*用于标记num[0][0]是否被选入   0:未选入。 1:选入*/  
  82.             dfs(num[1][0], 1, 0);  /*选B点开始搜索*/    
  83.         }  
  84.         if (minValue != 100)       /*找到*/  
  85.             printf("%d\n",minValue);  
  86.         else   
  87.             printf("0\n");  
  88.     }  
  89.       
  90.     return 0;  
  91. }   

 

第三次修正,克服如下形式不能搜索到的问题:

思想:将每个格子都作为起点开始搜索一遍~  时间复杂度大幅度增加,但是能搜索所有可能的剪切方案。

具体实现,见主函数里面的双重for循环。o(╯□╰)o

 

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. /* 
  2.     Name: 蓝桥杯 剪格子  
  3.     Copyright: Analyst  
  4.     Author: Analyst  
  5.     Date: 05/03/14 15:31 
  6.     Description: dev-cpp 5.5.3 
  7. */  
  8. #include <stdio.h>  
  9.   
  10. int  m, n, half=0, sum = 0, minValue = 100, count = 0, select = 0, tnum;  
  11. int num[10][10]={0}, flag[10][10] = {0};  
  12. int xzb[]={-1,1,0,0};   /*上下左右*/  
  13. int yzb[]={0,0,-1,1};  
  14.   
  15. int isok(int x, int y)  /*判断传入的坐标值是否能被选入*/  
  16. {  
  17.     int yes = 0;        /*不越界并且不超出和一半*/  
  18.     if ((x >= n || x < 0) || (y >= m || y < 0) || (flag[x][y] == 1) || (sum+num[x][y] > half))  
  19.         yes = 1;    /*冲突*/     
  20.     return yes;  
  21. }  
  22.   
  23. void back(int value, int x, int y)   /*执行回退处理*/  
  24. {  
  25.     if (x == 0 && y == 0)            /*如果将num[0][0]回溯掉,则标记为未选入*/  
  26.         select = 0;  
  27.     --count;  
  28.     sum -= value;  
  29.     flag[x][y] = 0;   
  30. }  
  31.   
  32. void dfs(int value, int x, int y)    /*dfs搜索*/  
  33. {  
  34.     int i, t1, t2;  
  35.       
  36.     if (x == 0 && y == 0)     /*主要用于B点开始的搜素,判断num[0][0]有没有被选入*/  
  37.         select = 1;  
  38.     flag[x][y] = 1;           /*标记为已走过*/  
  39.     ++count;                  /*已走过点的个数*/  
  40.     sum += value;             /*已走过的点的和*/  
  41.     if (sum == half)  
  42.     {                           
  43.         if (select == 1)      /*如果num[0,0]被选入,主要用于非[0,0]点开始的搜索*/  
  44.             tnum = count;  
  45.         else   
  46.             tnum = n * m - count;  
  47.         minValue = minValue > tnum ? tnum : minValue; /*选出剪掉点数最少的方案*/  
  48.     }  
  49.     else   
  50.     {  
  51.         for (i = 0; i < 4; ++i)  /*按 上下左右 顺序遍历*/  
  52.         {  
  53.             t1 = x + xzb[i];     /*引入t1,t2目的是使传入的x,y值不变,便于下面的回退*/  
  54.             t2 = y + yzb[i];  
  55.             if (isok(t1, t2) == 1)/*是否可走*/  
  56.                 continue;  
  57.             dfs(num[t1][t2], t1, t2);  
  58.         }  
  59.     }  
  60.     back(value, x, y);           /*如果上下左右都不可走:回退*/  
  61. }  
  62.   
  63. int main()  
  64. {  
  65.     int i, j, maxNum = 0, all = 0;  
  66.       
  67.     scanf ("%d%d", &m, &n);   
  68.     for (i = 0; i < n; ++i)  
  69.         for (j = 0; j < m; ++j)  
  70.         {  
  71.             scanf ("%d", &num[i][j]);  
  72.                 all += num[i][j];   /*all为总和*/  
  73.             if (num[i][j] > maxNum)   /*找出最大值*/  
  74.                 maxNum = num[i][j];  
  75.         }  
  76.     if (all % 2 != 0 || maxNum > all / 2)/*如果和为基数或者最大值大于总和一半*/  
  77.         printf("0\n");  
  78.     else  
  79.     {  
  80.         half = all / 2;  
  81.         /*将格子每个点都作为起点搜索一遍,两个for循环*/  
  82.         for (i = 0; i < n; ++i)  
  83.             for (j = 0; j < m; ++j)   
  84.             {  
  85.                 select = 0;   
  86.                 dfs(num[i][j], i, j);           
  87.             }  
  88.         if (minValue != 100)       /*找不到*/  
  89.             printf("%d\n",minValue);  
  90.         else   
  91.             printf("0\n");  
  92.     }  
  93.       
  94.     return 0;  
  95. }   

 

 

 

提交序号姓名试题名称提交时间  代码长度  语言  C  C++  JAVA   评测结果  正确  错误  编译出错  运行错误  运行超时  内存超限   得分  100  1~99  0 CPU使用  内存使用  评测详情
126131Analyst剪格子03-07 16:411.902KBC正确1000ms784.0KB评测详情
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值