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

说在前面

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



关键代码

题:

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 }

完整代码(可运行,详细注释)

原文地址:https://www.cnblogs.com/cc1997/p/10542353.html

时间: 2024-07-29 18:01:59

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

算法系列之图--拓扑排序

本文介绍使用深度先搜索对向无环图(DAG)进行拓扑排序. 对于一个有向无环图G=(V,E)来说,其拓扑排序是G中所有结点的一种线性次序,该次序满足如下条件:如果G包含边(u,v)则结点u在拓扑排序中处于结点v的前面(若图G包含一个环路则不可能排出一个线性次序).可将图中的拓扑排序看成是将图的所有结点在一条水平线上排开,图中所有边都从左指向右. 给一个拓扑图如下示: 拓扑排序算法与DFS相似,但是在拓扑排序的过程中,每个结点都是后与其临接链表里的结点而放入Stack中. 具体代码如下示: 1 #i

数据结构:图--拓扑排序

拓扑排序 拓扑排序 在实际应用中,有向图的边可以看做是顶点之间制约关系的描述.把顶点看作是一个个任务,则对于有向边<Vi,Vj>表明任务Vj的完成需等到任务Vi完成之后,也就是说任务Vi先于任务Vj完成.对于一个有向图,找出一个顶点序列,且序列满足:若顶点Vi和Vj之间有一条边<Vi,Vj>,则在此序列中顶点Vi必在顶点Vj之前.这样的一个序列就称为有向图的拓扑序列(topological order). 步骤 从有向图中选取一个没有前驱(入度为0)的顶点输出. 删除图中所有以它为

HDU4857——逃生(反向建图+拓扑排序)

逃生 Description 糟糕的事情发生啦,现在大家都忙着逃命.但是逃命的通道很窄,大家只能排成一行. 现在有n个人,从1标号到n.同时有一些奇怪的约束条件,每个都形如:a必须在b之前.同时,社会是不平等的,这些人有的穷有的富.1号最富,2号第二富,以此类推.有钱人就贿赂负责人,所以他们有一些好处.负责人现在可以安排大家排队的顺序,由于收了好处,所以他要让1号尽量靠前,如果此时还有多种情况,就再让2号尽量靠前,如果还有多种情况,就让3号尽量靠前,以此类推.那么你就要安排大家的顺序.我们保证一

算法小练#1 - Dany Yang

开始记录每周做过的算法题,这是第一周,新的开始 1021. 删除最外层的括号 题目要求如下: 有效括号字符串为空 ("")."(" + A + ")" 或 A + B,其中 A 和 B 都是有效的括号字符串,+ 代表字符串的连接.例如,"","()","(())()" 和 "(()(()))" 都是有效的括号字符串. 如果有效字符串 S 非空,且不存在将其拆分为 S

算法导论22.4拓扑排序 练习总结 (转载)

22.4-1 给出算法 TOPOLOGICAL-SORT 运行于图 22-8 上时所生成的结点次序.这里的所有假设和练习 22.3-2 一样. ANSWER:   22.4-2 请给出一个线性时间的算法,算法的输入为一个有向无环图 G = (V, E) 以及两个结点 s 和 t,算法的输出是从结点 s 到结点 t 之间的简单路径的数量.例如,对于图 22-8 所示的有向无环图,从结点 p 到结点 v 一共有 4 条简单路径,分别是 pov.poryv.posryv 和 psryv.(本题仅要求计

hdu 4857 逆向建图+拓扑排序 ***

题意:糟糕的事情发生啦,现在大家都忙着逃命.但是逃命的通道很窄,大家只能排成一行.现在有n个人,从1标号到n.同时有一些奇怪的约束条件,每个都形如:a必须在b之前.同时,社会是不平等的,这些人有的穷有的富.1号最富,2号第二富,以此类推.有钱人就贿赂负责人,所以他们有一些好处.负责人现在可以安排大家排队的顺序,由于收了好处,所以他要让1号尽量靠前,如果此时还有多种情况,就再让2号尽量靠前,如果还有多种情况,就让3号尽量靠前,以此类推.那么你就要安排大家的顺序.我们保证一定有解. 链接:点我 题目

[POI2015][bzoj4383] Pustynia [线段树优化建图+拓扑排序]

题面 bzoj权限题传送门 luogu传送门 思路 首先,这个题目显然可以从所有小的点往大的连边,然后如果没环就一定可行,从起点(入读为0)开始构造就好了 但是问题来了,如果每个都连的话,本题中边数是$O(n^2)$级别的,显然会挂 发现两条性质: 1.所有的限制条件中,给定的总点数不超过3e5个 2.是一个点比一段区间大 第二个条件决定了我们可以利用线段树优化建图,而第一个条件告诉了我们,本题的总边数应该是$sumk\astlog_2n$级别的 那么就做完了 注意拓扑排序的时候有个技巧,把连向

图-&gt;有向无环图-&gt;拓扑排序

文字描述 关于有向无环图的基础定义: 一个无环的有向图称为有向无环图,简称DAG图(directed acycline graph).DAG图是一类较有向树更一般的特殊有向图. 举个例子说明有向无环图的应用.假如有一个表达式: ((a+b)*(b*(c+d))+(c+d)*e)*((c+d)*e), 可以用之前讨论的二叉树来表示,也可以用有向无环图来表示,如下图.显然有向无环图实现了对相同子式的共享,从而比二叉树更节省空间. 关于拓扑排序的基础定义: 由某个集合上的一个偏序得到该集合上的一个全须

算法详解之拓扑排序

名词解释 ·(点的)度:对于无向图,和某个点相连的边条数 ·入度:对于有向图,终点是该点的边条数 ·出度:对于有向图,起点是该点的边条数 ·(两点间)路径:从起点点依次沿着边移动到下一个点,直到终点所经过的点和/或边若未有向图要求只能从边的起点移动到边的终点 ·圈:从一个点出发到自己的路径,常常被称作环 ·有向无环图(DAG):不含有环的有向图 拓扑排序 ·和数组的排序没什么关系 ·对DAG的顶点进行排序,结果要求 每个顶点出现且仅出现一次 对于顶点对(u,v),若排序后u在v前,则不存在v到u