【題目鏈接】UVA-11212
【題意】給一串長度爲n的數字序列(n<10),可以隨意複製某段再粘貼,問最少操作幾次能變爲連續上升序列。
【樣例】
Sample Input
6
2 4 1 5 3 6
5
3 4 5 1 2 0
Sample Output
Case 1: 2
Case 2: 1
【分析】
做的第一道IDA*。
當然應該考慮一下爲什麼不得不走向IDA*。
首先考慮單純的dfs:對於單次的操作帶來的下一個狀態,因爲n很小,所以還是可以一一枚舉的。因爲操作次數沒有明確上限,這樣的話搜索會不斷加深,如果要終止搜索,需要保存已訪問過的狀態(總狀態我最多有9!=362880個),遇到已訪問的狀態就continue。即便這樣的話,那麼在每一個操作點之後,都要不斷加深,遍歷掉所有的狀態才能終止,並且第一次返回的未必是最小操作數。如果設爲操作數達到n-1就返回,那麼每個操作結點上也要一直加深到最大深度纔會返回。這樣的搜索量非常大。
寫到這裏,卡住了,因爲想了一想bfs的可行性,不是很能確定。
幸好有別的人對這道題做了我想看到的分析,貼一下:UVA-11212分析。這樣一來,這道題採用IDA*,相比DFS和BFS的優勢就很清楚了。
迭代加深搜索(iterative deepening):從小到大枚舉深度上限maxd,每次執行只考慮深度不超過maxd的結點。這樣,只要解的深度有限,則一定可以在有限時間內枚舉到。這種搜索方式格外適合那些深度並不明確的問題,如埃及分數問題。要在此基礎上再減少搜索量,設深度上限爲 maxd,當前結點n的深度爲g(n),樂觀估價函數爲h(n),則當g(n)+h(n)>maxd時應該剪枝。這樣的算法就是IDA*。
IDA*的關鍵在於啓發函數。對於本題,考慮後繼不正確的數字個數h,可以證明每次剪切時h最多減少3,因此當3d+h>3maxd時可以剪枝,其中d爲當前深度,maxd爲深度限制。(這是劉汝佳寫的,看着很有道理,但是自己能不能獨立設計出來還真是一個大問題啊)。
哎,一篇博客,重點部分全是別人寫的。
【代碼】
*注意判一下答案爲0的情況
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int p[10],n;
int maxd;
int wnum(){
int num=0;
for(int i=1;i<n;i++){
if(p[i+1]!=p[i]+1)
num++;
}
return num;
}
bool dfs(int d,int h){
bool ok=false;
if(d==maxd){
if(wnum()==0){
return true;
}
else
return false;
}
if(d*3+h>3*maxd)
return false;
int cop[10];//定義爲局部變量
for(int i=1;i<=n;i++)
cop[i]=p[i];
for(int len=n-1;len>=1;len--){
for(int s=1;s<=n-len+1;s++){
for(int e=s+len;e<=n;e++){
memcpy(p+s,cop+s+len,(e-s+1-len)*sizeof(int));
memcpy(p+e+1-len,cop+s,len*sizeof(int));
if(dfs(d+1,wnum())){
return true;
}
memcpy(p+1,cop+1,n*sizeof(int));
}
}
}
return false;
}
int main(){
int cas=0;
while(scanf("%d",&n)==1&&n){
cas++;
for(int i=1;i<=n;i++){
scanf("%d",&p[i]);
}
if(wnum()==0){
printf("Case %d: %d\n",cas,0);
continue;
}
for(maxd=1;maxd<n;maxd++){
if(dfs(0,wnum()))
break;
}
printf("Case %d: %d\n",cas,maxd);
}
return 0;
}