[算法小练][图][拓扑排序+深度优先搜索] 平板涂色问题

Posted cc1997

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[算法小练][图][拓扑排序+深度优先搜索] 平板涂色问题相关的知识,希望对你有一定的参考价值。

说在前面

本题是一道经典题目,多做经典题目可以节省很多学习时间,比如本题就包含了许多知识:回溯+剪枝+拓扑排序+深度优先搜索。[动态规划方法另作讨论]


 

关键代码

 题:

CE数码公司开发了一种名为自动涂色机(APM)的产品。它能用预定的颜色给一块由不同尺寸且互不覆盖的矩形构成的平板涂色。

为了涂色,APM需要使用一组刷子。每个刷子涂一种不同的颜色C。APM拿起一把有颜色C的刷子,并给所有颜色为C且符合下面限制的矩形涂色:

为了避免颜料渗漏使颜色混合,一个矩形只能在所有紧靠它上方的矩形涂色后,才能涂色。例如图中矩形F必须在C和D涂色后才能涂色。注意,每一个矩形必须立刻涂满,不能只涂一部分。

写一个程序求一个使APM拿起刷子次数最少的涂色方案。注意,如果一把刷子被拿起超过一次,则每一次都必须记入总数中。

 

输入输出格式

输入格式:
第一行为矩形的个数N。下面有N行描述了N个矩形。每个矩形有5个整数描述,左上角的y坐标和x坐标,右下角的y坐标和x坐标,以及预定颜色。

 

颜色号为1到20的整数。

 

平板的左上角坐标总是(0, 0)。

 

坐标的范围是0..99。

 

N小于16。

 

输出格式:
输出至文件paint.out,文件中记录拿起刷子的最少次数。

 

输入输出样例
输入样例#1:
7
0 0 2 2 1
0 2 1 6 2
2 0 4 2 1
1 2 4 4 2
1 4 3 6 1
4 0 6 4 1
3 4 6 6 2

 

输出样例#1:
3

 

技术图片

 

技术图片
  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<cmath>
  5 #include<algorithm>
  6 #include<cstdlib>
  7 using namespace std;
  8 
  9 bool d=false;
 10 int de[20]={0};                                //de数组表示需要涂该颜色的板块数量,如de[2]=3表示需要2号颜色的板块有3个 
 11 int n,m,ans=999,b[20],rel[20][20];             //b数组代表该板块是否被涂//rel[i][j]表示第i个板块是否紧邻上方第j板块(如rel[3][1]表示编号为3的板块上方紧邻编号为1的板块[1涂完3才能涂]) 
 12  
 13 /*表示板块的结构体(其中a1b1 该砖左上角坐标 a2b2 右下角坐标 x 颜色。例如:(0,2)(1,6)表示一个板块对应的 a1=0,a2=1 ; b1=2,b2=6 )*/
 14 struct rect
 15 {
 16     int a1,b1,a2,b2,x;
 17 }rectArr[20];
 18 /*结构体结束*/
 19 
 20 /*比较函数(作为sort的第三个参数) */
 21 int ccmp(rect a,rect b)                        
 22 {
 23     if(a.a1!=b.a1) return a.a1<b.a1;        //a板块的左上角纵坐标a1和b板块左上角纵坐标a1不等,表示两个板块不是齐平的,返回前者小于后者说明按纵坐标从小到大排序 (即图中从上到下) 
 24     return a.b1<b.b1;                        //a板块的左上角纵坐标a1和b板块左上角纵坐标a1相等,表示两个板块是齐平的, 返回前者小于后者说明在齐平的时候按横坐标从小到大排序(即图中从左到右) 
 25 }
 26 /*比较函数结束*/
 27 
 28 /*判断编号为x的块能不能涂*/
 29 bool canPaint(int x)                        
 30 {
 31     for(int i=1;i<=n;i++)
 32         if(rel[x][i]&&!b[i]) return false;     //如果i砖下面紧邻x,但i没涂过,返回false,即不能涂x 
 33     return true;                            ///否则x可以涂 
 34 }
 35 /*判断结束*/
 36  
 37 /*遍历开始*/
 38 void dfs(int brushCnt,int painted,int lastColor)    //brush为换刷子次数 painted为涂过颜色的砖 last 上次涂的颜色
 39 {
 40     if(brushCnt>=ans) return;                         //当前涂色次数大于等于当前答案,直接退出(ans记录了当前最优的换刷子次数) 【最优性剪枝】 
 41     if(painted==n)                                     //涂完了,记录答案
 42     {
 43         ans=brushCnt;
 44         return;
 45     }
 46     
 47     /*枚举颜色(如:样例输入中只有两种颜色所以m = 2)*/
 48     for(int brushColor=1; brushColor<=m; brushColor++)                             
 49     {
 50         int painting=0;                                                 //用来记录现在用这个颜色涂的板块数 
 51         
 52         /*if有这个颜色,并且这种颜色上次没用过*/ /*若不符合则条件,换下一个颜色(继续枚举颜色的循环),继续判断*/
 53         if(de[brushColor] && brushColor != lastColor)
 54         {
 55             /*涂色(遍历所有的板块,能涂就涂)*/ 
 56             for(int j=1; j<=n; j++)                             
 57             {
 58                 if(!b[j] && rectArr[j].x==brushColor && canPaint(j))    //如果没涂过该板块 并且 要涂的是当前刷子颜色 并且 能涂
 59                 {
 60                     b[j]=1;                                                //涂色(标记该板块已涂色) 
 61                     painting++;                                            //当前颜色涂色数+1 
 62                 }
 63                 else if(b[j] && rectArr[j].x==brushColor ) b[j]++;        //如果涂过该板块 并且 涂的是当前刷子颜色 (即之前已经涂色过且当前刷子也可以涂),则b[j]++,保存每一步的状态,便于回溯。
 64             }
 65            /*涂色完毕*/ 
 66            
 67            if(painting > 0) dfs(brushCnt+1,painted+painting,brushColor);        
 68            //如果涂了板块,换刷子继续涂:一、换刷子次数brushCnt+1;二、已涂色的板块数painted + 这一轮所涂板块数painting;三、当前颜色brushColor 
 69            //如果没涂板块,[最终都涂完了,所有板块都涂不了了==》没涂板块]    进入回溯 
 70            
 71            /*回溯*/
 72            for(int j=n;j>=1;j--)                                         //回溯一步
 73            {
 74                 if(b[j]==1 && rectArr[j].x==brushColor && canPaint(j))    //如果j板块已经涂色(只有一层)并且 涂的是当前刷子的颜色
 75                 {
 76                     b[j]=0;                                                //清除该颜色(标记该板块未涂色)
 77                     painting--;                                            //已涂色板块数-1 
 78                 }
 79                 else if(b[j]>1 && rectArr[j].x==brushColor) b[j]--;     //如果j板块已经涂色(不止一层)并且 涂的是当前刷子的颜色 则回溯
 80            }
 81            /*回溯结束*/
 82         }
 83         /*if结束*/
 84     }
 85     /*枚举颜色结束*/ 
 86 }
 87 /*遍历结束*/
 88 
 89 
 90 int main()
 91 {
 92     /*输入部分开始*/
 93     cin>>n;                                //板块个数 
 94     for(int i=1;i<=n;i++)                //循环录入板块信息 
 95     {
 96         scanf("%d%d%d%d%d",&rectArr[i].a1, &rectArr[i].b1, &rectArr[i].a2, &rectArr[i].b2, &rectArr[i].x);            //a[i]这个块的三个信息 一:a1,b1(左上角坐标)二:a2,b2(右下角坐标)三:所需颜色x 
 97         rectArr[i].a1++;rectArr[i].b1++;              //个人习惯把左上角坐标+1,就可以看成它左上角所占的方格//例如 0 0 2 2 +1后为 1 1 2 2 ,表示该砖左上角,右下角所占的方格。为什么要表示左下角+右下角所占方格? 
 98         de[rectArr[i].x]++;                            //de[1]和de[2]颜色数量记录,最后统计出所有颜色各多少个板块 (实际可能有20种颜色)
 99     }
100     /*输入部分结束*/
101     
102     for(int i=1;i<=20;i++) if(de[i]) m=i;             //求20个颜色要用上几个
103     
104     sort(rectArr+1,rectArr+n+1,ccmp);                  //按左上角坐标大小从小到大排序(ccmp函数==>先考虑纵,再考虑横)[例如:C板块在左,D板块在右,但D板块比C板块高一些,则D排在前] 
105     
106     /*开始给板块间添加关系(即每个板块上面紧邻哪些板块。[申明了要涂它们的前提是 先涂哪个板块])*//*先决条件*/
107     for(int i=2;i<=n;i++)
108         for(int j=i-1;j>=1;j--)             
109             if(rectArr[i].a1==rectArr[j].a2+1 && ((rectArr[i].b1>=rectArr[j].b1 && rectArr[i].b1<=rectArr[j].b2) || (rectArr[i].b2>=rectArr[j].b1 && rectArr[i].b2<=rectArr[j].b2)))    //如果i板块的上边缘紧邻j板块下边缘 且 两砖横坐标有重叠==>即j砖为i砖紧邻上面的砖
110                 rel[i][j]=1;                
111     /*结束给板块间添加关系(rel数组赋值结束)*/ 
112     
113     dfs(0,0,0);                            //开始
114     
115     cout<<ans;                            //结果
116     return 0;
117 }
完整代码(可运行,详细注释)

 

以上是关于[算法小练][图][拓扑排序+深度优先搜索] 平板涂色问题的主要内容,如果未能解决你的问题,请参考以下文章

通用的深度优先搜索+图的应用1:拓扑排序

图相关算法

基本算法——深度优先搜索(DFS)和广度优先搜索(BFS)

深度优先搜索算法解释下?

深度优先搜索原理与实践(java)

图的深度优先搜索及拓扑排序