UVA 11212 Editing a Book(IDA*)

【題目鏈接】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;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章