浅谈分治 —— 洛谷P1228 地毯填补问题 题解
如果想看原题网址的话请点击这里:地毯填补问题
原题:
题目描述 相传在一个古老的阿拉伯国家里,有一座宫殿。宫殿里有个四四方方的格子迷宫,国王选择驸马的方法非常特殊,也非常简单:公主就站在其中一个方格子上,只要谁能用地毯将除公主站立的地方外的所有地方盖上,美丽漂亮聪慧的公主就是他的人了。公主这一个方格不能用地毯盖住,毯子的形状有所规定,只能有四种选择(如图): (此处图误见后处的图) 并且每一方格只能用一层地毯,迷宫的大小为 2^k * 2^k的方形。当然,也不能让公主无限制的在那儿等,对吧?由于你使用的是计算机,所以实现时间为1s。 输入格式 输入文件共 22 行。 第一行:kk,即给定被填补迷宫的大小为 2^k * 2^k 第二行:x,yx,y,即给出公主所在方格的坐标(xx 为行坐标,yy 为列坐标),xx 和 yy 之间有一个空格隔开。 输出格式 将迷宫填补完整的方案:每一补(行)为x\ y\ cx y c(x,yx,y 为毯子拐角的行坐标和列坐标, cc 为使用毯子的形状,具体见上面的图 11,毯子形状分别用 1,2,3,41,2,3,4 表示,x,y,cx,y,c 之间用一个空格隔开)。 输入输出样例 输入 #1 复制 3 3 3 输出 #1 复制 5 5 1 2 2 4 1 1 4 1 4 3 4 1 2 4 4 1 2 7 3 1 5 4 1 8 3 3 6 3 4 8 1 7 2 2 5 1 4 6 3 2 8 1 2 8 4 1 7 7 1 6 6 1 5 8 3 8 5 2 8 8 1 说明/提示 事实上感觉四个的形状分别是这样(仅供参考,如果有问题联系 icy) 图见原题 spj 报错: cc 越界 x,yx,y 越界 mp[x][y]mp[x][y] 已被占用 mp[x][y]mp[x][y] 从未被使用
首先在分析该题之前ROS先解释一下这里的行坐标和列坐标:
此处的行坐标表示的是第几行,而列坐标表示的是第几列;此处的坐标定义与我们平常使用的平面直角坐标系中的坐标前后表示意义正好相反,所以需要解释一下(一开始ROS就是因为没有看懂这点所以写出了一些前后矛盾的代码)
好的行坐标和列坐标的意义解释完了,现在开始分析题目:
先来解析题目构建算法,那么这道题我们看到被洛谷贴上了“分治”的标签所以一定要用分治的思想。
先来解析题目构建算法,我们一开始来看这道题就想一种最暴力的算法:可不可以尝试给每个位置都尝试能否铺上4种地毯中的一种然后用一个vis数组来储存是否被访问过,如果其中出现了一块四种地毯都铺不了的位置时我们则回溯到上一状态呢?然而经过大概分析可以得知这一算法是肯定不行的,就单单给每个位置都访问一遍并且假设从一开始到最后永不回溯且每次地毯都恰好正确就需要2^2k次访问,但是这是不可能的,所以这种暴力dfs的做法就算是最好的情况下才会过但这种情况出现的可能性为0(除非有人无聊到专门做一个为了给这种做法的人得分的数据……)。所以这种暴力dfs的算法直接pass
那么我们在分析之后将问题简化:如果我们面对的问题是对于一个有四个格的正方形方块,我们知道公主站在其中一个格子上那么我们如何铺地毯呢?这显然是一个很简单的问题:把地毯铺在剩下三个剩余空间的地板上就可以了。(①)
我们的算法就是从这种思想(①)入手的。
没错这种想法看似很简单但是我们却可以从这种简单的思想中寻找我们需要的分治算法思想。
那么如果我们我们想象公主站在一个有2^k * 2^k个地板的方形上,那么我们可以将这个大的方形想象成4个大块,而公主则想象站在四个大块的其中一个上,那么我们的问题就变成了(①)中的问题,只需要把地毯铺在剩余3块大块上就可以了。那么我们就做完了。(②)
那么我们该把这个我们假想的这三块地毯铺在哪里呢?(③)
就铺在这一个方形的中间4个小地板的3个大块所对应的小地板上。(④)
然后我们对于那个有公主站着的大块我们再“把视角拉近”,把这一个大块分成4个小块。然后进行(②)(③)(④)的操作。
这就是分治的思想,即将问题一步步分解成小问题从而对分解出来的小问题求解最终就求出了想要的整体解。
其实对于(①)我们还可以有另一种解释,那就是我们可以将这四个大块的状态(公主是否站在上面)压缩到最中间的4个小方块上,所以我们就可以理所当然的将地毯铺在最中间的4的块中的3个上了。
那么下面是代码实现:
(说实话ROS算法思想其实早就懂了但是代码一直不会实现今天才把代码写完)
1 #include<bits/stdc++.h> 2 using namespace std; 3 int k,x,y; 4 void dfs(int a,int b,int c,int e,int f); 5 int ksm(int x,int y); 6 int main(){ 7 scanf("%d%d%d",&k,&x,&y); 8 dfs(x,y,k,1,1); 9 return 0; 10 } 11 int ksm(int x,int y){ 12 if(x==0||x==1) return x; 13 if(y==0) return 1; 14 if(y==1) return x; 15 int tmp=ksm(x,y/2); 16 if(y&1){ 17 return tmp*tmp*x; 18 } 19 return tmp*tmp; 20 } 21 void dfs(int a,int b,int c,int e,int f){ 22 if(c==0) return ; 23 int tmp=ksm(2,c-1); 24 if(a<=e+tmp-1){ 25 if(b<=f+tmp-1){ 26 printf("%d %d 1\n",e+tmp,f+tmp); 27 dfs(e+tmp,f+tmp,c-1,e+tmp,f+tmp); 28 dfs(e+tmp,f+tmp-1,c-1,e+tmp,f); 29 dfs(e+tmp-1,f+tmp,c-1,e,f+tmp); 30 if(c>=2) dfs(a,b,c-1,e,f); 31 return ; 32 } 33 else{ 34 printf("%d %d 2\n",e+tmp,f+tmp-1); 35 dfs(e+tmp,f+tmp-1,c-1,e+tmp,f); 36 dfs(e+tmp,f+tmp,c-1,e+tmp,f+tmp); 37 dfs(e+tmp-1,f+tmp-1,c-1,e,f); 38 if(c>=2) dfs(a,b,c-1,e,f+tmp); 39 return ; 40 } 41 } 42 if(a>e+tmp-1){ 43 if(b>f+tmp-1){ 44 printf("%d %d 4\n",e+tmp-1,f+tmp-1); 45 dfs(e+tmp-1,f+tmp-1,c-1,e,f); 46 dfs(e+tmp-1,f+tmp,c-1,e,f+tmp); 47 dfs(e+tmp,f+tmp-1,c-1,e+tmp,f); 48 if(c>=2) dfs(a,b,c-1,e+tmp,f+tmp); 49 return ; 50 } 51 else{ 52 printf("%d %d 3\n",e+tmp-1,f+tmp); 53 dfs(e+tmp-1,f+tmp,c-1,e,f+tmp); 54 dfs(e+tmp-1,f+tmp-1,c-1,e,f); 55 dfs(e+tmp,f+tmp,c-1,e+tmp,f+tmp); 56 if(c>=2) dfs(a,b,c-1,e+tmp,f); 57 return ; 58 } 59 } 60 return ; 61 }
由于ROS原来正是因为有一些困惑再加上ROS及其讨厌看题解(ROS认为这会影响到人的自主思维)而导致不会代码实现所以ROS把我当时的困惑说出来并进行解答:
1.
Q:如何实现我在搜索哪一个“大块”?
A:对于这一问题,ROS一开始真的不知道怎么表示。但后来ROS发现有于我们进行分治的思想所以我们可以知道我们这一次所进行搜索的正方形块边长是多少,所以我们可以通过一个块的坐标和正方形边长就可以得到我们在搜索的这个大块的具体坐标范围了。
2.
Q:有没有可能会出现最终有的地板没有相应的可以铺的地毯的情况?
A:这是不可能的。因为我们已经得知方形的边长为2^k所以一步步分下去(每一次分成4个等大的小块)一定是可以恰好有唯一情况分完的。
最后总结:分治就是把一个整体的大问题分成一个个可以解决的小问题然后解决小问题的过程。这种思想和递归类似,而与递归不同的是递归通常还是长时间消耗的代名词,而分治则通常不会消耗过长时间(以为没有调用自身的过程而把问题分解了)