水叮當的舞步 深搜
背景 Background
水叮當得到了一塊五顏六色的格子形地毯作為生日禮物,更加特別的是,地毯上格子的顏色還能隨著踩踏而改變。
為了討好她的偶像虹貓,水叮當決定在地毯上跳一支輕盈的舞來賣萌~~~
描述 Description
地毯上的格子有N行N列,每個格子用一個0~5之間的數字代表它的顏色。
水叮當可以隨意選擇一個0~5之間的顏色,然後輕輕地跳動一步,左上角的格子所在的聯通塊裏的所有格子就會變成她選擇的那種顏色。這裏連通定義為:兩個格子有公共邊,並且顏色相同。
由於水叮當是施展輕功來跳舞的,為了不消耗過多的真氣,她想知道最少要多少步才能把所有格子的顏色變成一樣的。
輸入格式 InputFormat
每個測試點包含多組數據。
每組數據的第一行是一個整數N,表示地攤上的格子有N行N列。
接下來一個N*N的矩陣,矩陣中的每個數都在0~5之間,描述了每個格子的顏色。
N=0代表輸入的結束。
輸出格式 OutputFormat
對於每組數據,輸出一個整數,表示最少步數。
樣例輸入 SampleInput
2
0 0
0 0
3
0 1 2
1 1 2
2 2 1
0
樣例輸出 SampleOutput
0
3
數據範圍和註釋 Hint
對於30%的數據,N<=5
對於50%的數據,N<=6
對於70%的數據,N<=7
對於100%的數據,N<=8,每個測試點不多於20組數據。
這題首先告訴我們,一定要審題,仔細審題,用心審題。
一開始看這道題,以為好難,要怎麽怎麽復雜地廣搜。然後經過楊之典大觸的指出後,我才知道其實每次只是動左上角那個聯通塊,於是問題就清晰了。
我們經過一定的思考,這題廣搜其實沒有深搜好用,於是我們就用深搜來解決這道題。
那麽用深搜就一定要有一定的剪枝,所以我們來看看怎麽剪枝。
首先第一個剪枝很明顯,如果在ans步內已經搜到過目標,那麽之後搜索的深度肯定在這個範圍之內。
然後是第二個剪枝。假設我們現在圖中有a中顏色,現在是b步,已經在ans步內搜到過答案,那麽如果a+b>ans的話也沒必要往下搜了,因為每次最多改變一種顏色。
第三個剪枝,假設我們左上角聯通塊周圍的顏色是2,4,那麽我們這時還要不要把左上角的聯通塊變成五個顏色往下搜呢?顯然,我們只要把左上角的聯通塊變成2或4再往下搜,
變成其他顏色顯然不是最優的。
第四個剪枝,是建設在第三個剪枝之上的,假設我們左上角的聯通塊周圍的顏色是2,4,而且2有4個,4只有2個,那個我們先搜哪個呢?當然是先搜2,。
講了這麽多,感覺好麻煩,其實,真的很麻煩。
代碼:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define ll long long #define il inline #define db double using namespace std; il int gi() { int x=0,y=1; char ch=getchar(); while(ch<‘0‘||ch>‘9‘) { if(ch==‘-‘) y=-1; ch=getchar(); } while(ch>=‘0‘&&ch<=‘9‘) { x=x*10+ch-‘0‘; ch=getchar(); } return x*y; } il ll gl() { ll x=0,y=1; char ch=getchar(); while(ch<‘0‘||ch>‘9‘) { if(ch==‘-‘) y=-1; ch=getchar(); } while(ch>=‘0‘&&ch<=‘9‘) { x=x*10+ch-‘0‘; ch=getchar(); } return x*y; } int n; int ans; int map[10][10]; int pre[10][10]; bool v[10][10]; il bool check()//檢查是否滿足條件 { int now=map[1][1]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(map[i][j]!=now) return 0; return 1; } il void hzr()//記錄修改前的圖 { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) pre[i][j]=map[i][j]; } il void hzrr()//用來回溯 { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) map[i][j]=pre[i][j]; } il int count()//用來做第二個剪枝的函數 { int sum=0; bool vis[45]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(!vis[map[i][j]]) { sum++; vis[map[i][j]]=1; } return sum; } struct col { int co,s; }c[10];//用來記錄聯通塊周圍的顏色種類和數量的結構體 bool cmp(col a,col b) { return a.s>b.s; } bool vis[10][10]; il void bfs()//bfs求聯通塊,並且記錄左上角的聯通塊周圍的顏色 { int head=0,tail=1; int t[100][2];//用來廣搜的隊列 for(int i=0;i<=5;i++) c[i].s=0,c[i].co=i;//初始化記錄的結構體 memset(vis,0,sizeof(vis)); int dis[5]={0,1,0,-1,0}; t[0][0]=1; t[0][1]=1; v[1][1]=1; vis[1][1]=1; while(head!=tail) { for(int i=0;i<4;i++) { int x=t[head][0]+dis[i],y=t[head][1]+dis[i+1]; if(vis[x][y]||x>n||x<1||y>n||y<1)//如果在界外或者已是聯通塊裏的 continue; if(map[x][y]!=map[1][1])//如果是周圍的不同顏色的就記錄 { c[map[x][y]].s++; continue; } vis[x][y]=1; t[tail][0]=x; t[tail++][1]=y; } head++; } } il void change(int x)//將聯通塊變成目標顏色 { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(vis[i][j]) map[i][j]=x; } viod dfs(int x) { if(x>ans)//第一個剪枝 return; if(check())//如果滿足條件,那麽就記錄 { if(ans>x) ans=x; return; } if(x+count()>ans)//第二個剪枝 return; hzr();//為回溯做準備 bfs();//求出聯通塊,並且記錄聯通塊周圍的顏色種類和數量 sort(c,c+5,cmp);//按周圍顏色出現的次數排序 for(int i=0;i<=5;i++) { if(!c[i].s) break; change(c[i].co);//把聯通塊都變成目標顏色 dfs(x+1);//往下搜 hzrr();//回溯 } } int main() { while(1) { n=gi(); if(!n) break; ans=2e8; memset(map,0,sizeof(map)); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) map[i][j]=gi(); dfs(0); printf("%d\n",ans); } return 0; }
水叮當的舞步 深搜