1. 程式人生 > >[算法小練][圖][拓撲排序+深度優先搜索] 平板塗色問題

[算法小練][圖][拓撲排序+深度優先搜索] 平板塗色問題

png 不同的 string %d 程序 using class info 完整

說在前面

本題是一道經典題目,多做經典題目可以節省很多學習時間,比如本題就包含了許多知識:回溯+剪枝+拓撲排序+深度優先搜索。[動態規劃方法另作討論]


關鍵代碼

題:

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 }
完整代碼(可運行,詳細註釋)

[算法小練][圖][拓撲排序+深度優先搜索] 平板塗色問題