UVa11212 编辑书稿(Editing a book)

题目链接:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=841&page=show_problem&problem=2153


题意:你有一篇n(2n9)个自然段组成的文章,希望将它们排列成1,2,…,n。可以用Ctrl+X(剪切)和Ctrl+V(粘贴)快捷键来完成任务。每次可以剪切一段连续的自然段,粘贴时按照顺序粘贴。注意,剪贴板只有一个,所以不能连续剪切两次,只能剪切和粘贴交替。例如,为了将{2,4,1,5,3,6}变为升序,可以剪切1将其放到2前,然后剪切3将其放到4前。再如,排列{3,4,5,1,2},只需一次剪切和一次粘贴即可——将{3,4,5}放在{1,2}后,或者将{1,2}放在{3,4,5}前。(本段摘自《算法竞赛入门经典(第2版)》)


使用算法:在本题中我们主要使用以下几个算法

1)迭代加深搜

2)启发式算法


迭代加深搜:

让我们先来讨论一下本题中的迭代加深搜,顾名思义,该算法共分为两个部分,一个是迭代,一个是深搜。“迭代”我们可以理解为每次将搜索的层数加1,为什么要这样加一呢?因为普通深搜每次深搜都是从头搜到尾的,假如我们的树有10层,而我们的答案却在第5层,那么我们是不是就浪费了时间。而限定搜索层数,我们就可以做到答案在第几层我们就搜到第几层,大大节省了时间。好,有的同胞们就问了,那为什么不直接用宽搜呢?那咱们再举一个例子,假如你树的每一层有100000000个节点,而我们的答案在第6层从左往右第二个怎么办呢?当然是深搜快了。好,这样又有人问了,假如我们的树共有9层,而我们的答案刚好在第九层,那么迭代加深搜就会比直接深搜多搜8次,就会花费更多的时间。这时我们还不如直接用深搜呢,这时,我们就需要之前提到的启发式算法了。


启发式算法:

启发式算法就是估计,估计你的答案所在位置,下面是一个二叉树可以帮助理解。

UVa11212 编辑书稿(Editing a book)_第1张图片

大家看假如我们要找9号节点,我们用迭代加深搜,就会搜(1,2,3)->(1,2,3,4,5,6,7)->(1,2,3,4,5,6,7,8,9)才能搜到9,假如我们直接用深搜就只用搜(1,2,4,8,9)。面对这样的情况,直接深搜当然更好,可万一我们每一层的宽度都非常大怎么办。答案就是用启发式算法来解决迭代加深搜的这个弊端。我们设置一个函数,使得他能估计答案大概在不在这层中。假如我们要找12这个节点,我们就设计一个函数来判断12在不在本层中,假如我们的迭代只有1次,树只有一层,我们就用启发式函数来估计答案是否在这层中,不在,我们就不递归了,直接跳过。再给大家提一下,其实每次我们迭代时,我们都可以理解为每次迭代都相当于生成一颗树,只是他们之间的层数不同,这提醒大家,我们每次把层数加一时,dfs都会重新从头开始搜。那好,既然我们每次都可以把它们理解为不同的树,那为什么之前我还要说启发式函数是用来判断答案是否在这层的呢?因为我个人认为这可以更好的理解为什么跌代加深搜与启发式算法配合能做到bfs的作用。bfs是一层一层搜,而启发式函数也是一层一层的判断。再给大家举个例子,假如我们要的答案是22,可5号节点和8号节点的值都为22,启发式函数可以帮助我们跳过迭代层数为1和2时要搜的数,确定答案不在1和2层中。使得他直接从迭代层数为3时搜,而这时的dfs就会应为迭代层数的限制无法搜到8号节点,搜到的是5号节点,因此可以确定迭代加深搜与启发式算法配合可以,高效的做到求最短路径。


言归正传,给大家讲了那么多迭代加深搜与启发式算法的目的是为了什么呢?还不是解题,下面是我解题的大概步骤:

输入->迭代->判断答案是否存在->存在就开始深搜,否则就继续迭代->搜到答案输出迭代层数。


重头戏来了,上代码:

#include
#include
#include
using namespace std;

const int maxn = 10;

int n,a[maxn];

bool ans_sort(){
    for(int i = 0;i < n-1;i++){
        if(a[i] >= a[i]+1)return false;//判断是否有序
    }
    return true;
}

int h(){
    int cnt =  0;
    for(int i = 0;i < n-1; i++)
        if(a[i]+1 != a[i+1])cnt++;//如果他加1等于他的下一个,则这个有序,否则无序,然后统计
    if(a[n-1] != n)cnt++;//判断最后一个数是否等于n
    return cnt;
}

bool dfs(int d,int maxd){
    if(d*3 + h() >= maxd*3)return false;//启发式函数估计
    if(ans_sort())return true;//判断是否顺序正确
    int o[maxn],b[maxn];
    memcpy(o,a,sizeof(a));
    for(int i = 0;i < n; i++){//设置剪切文本的最左边下标
        for(int j = i;j < n; j++){//设置剪切文本的最右边下标
            int cnt = 0;
            for(int k = 0;k < n;j++)
                if(k < i || k > j)b[cnt++] = a[k];//将有顺序的接在一起
            for(int k = 0;k <= cnt; k++){
                int cnt2 = 0;
                for(int p = 0;p < k; p++)a[cnt2++] = b[p];//插入文本之前的文本
                for(int p = i;p <= j;p++)a[cnt2++] = o[p];//插入的文本
                for(int p = k;p < cnt;p++)a[cnt2++] = b[p];//插入文本之后的文本

                if(dfs(d+1,maxd))return true;//深搜
                memcpy(a,o,sizeof(a));//还原a数组方便下次递归
            }

        }
    }
    return false;
}

int solve(){
    int max_ans = 5;//运用数学思维估计,老实说我也不懂为什么是5,不懂可以改成8.
    for(int maxd = 1;maxd < 5;maxd++){
        if(dfs(0,maxd))return maxd;//迭代加深搜
    }
    return max_ans;
}

int main(){
    while(scanf("%d",&n) == 1 && n){
        int kase = 1;
        for(int i = 0;i < n; i++)scanf("%d",&a[i]);
        printf("Case %d: %d\n",kase++,solve());
    }
}


你可能感兴趣的:(暴力)