《算法竞赛进阶指南》0x28IDA* POJ3460Booksort

题目链接:http://poj.org/problem?id=3460

题目给定一个乱序序列,长度为n,其中的数是1-n,操作是将其中一段插入任何一个位置,问最少需要多少次操作能够使得序列有序,超过四次直接输出5ormore

由于每个结点的分支数量达到了560,所以四层直接搜索时间复杂度很高,考虑IDA*,设置层数限制,并且使用未来估计,也就是最少需要的步数,如果最少需要的步数加上当前已经使用的步数

已经大于固定的步数的话及时回溯。注意,就算当前步数加上估计步数小于等于规定的步数也不一定能搜索到最终状态,所以在搜索失败之后返回一个return false

每一层中都需要单独保存这一层中用于扩展的状态,回溯时返回原状态并且转入下一个分支。

本次也学到了一个技术,s/t上取整等于(s+t-1)/t下取整。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 20;
int q[maxn];
int n;
int f(){//计算有最少的期望操作数 
    int tot=0;
    for(int i=0;i+1<n;i++)
    {
        if(q[i]+1!=q[i+1])tot++;
    }
    return (tot+2)/3;//tot/3上取整 
} 
bool check(){
    for(int i=0;i<n;i++)
        if(q[i]!=i+1)return false;
    return true;
}
bool dfs(int depth,int max_depth){
    if(depth+f() > max_depth)return false;//当前步数加上预估的最小步数大于指定最大步数
     if(check())return true;//成功搜索到目标状态
    
    int w[maxn];//每一层中单独记录原状态 
    //搜索每一个分支
    for(int len=1;len<=n;len++)//枚举长度 
        for(int l=0;l+len-1<n;l++){//枚举左端点 
            int r=l+len-1;
            for(int k=r+1;k<n;k++){    //枚举插入位置 ,向前插入与向后插入是重复的,只选向后的 
                memcpy(w,q,sizeof(q));
                //双指针扫描填数 
                int x,y;
                //将后面一段向前拼接 
                for(x=r+1,y=l;x<=k;x++,y++)q[y]=w[x];
                //前面一段向后拼接继续从y的位置开始 
                for(x=l;x<=r;x++,y++)q[y]=w[x];
                if(dfs(depth+1,max_depth)) return true;
                memcpy(q,w,sizeof(w));
            }
        }
    return false;         
} 
int main(){
    int T;
    cin>>T;
    while(T--){
        scanf("%d",&n);
        for(int i=0;i<n;i++)scanf("%d",&q[i]);
        //操作步数 
        int depth=0;
        while(depth<5 && !dfs(0,depth))depth++;
        
        if(depth>=5)puts("5 or more");
        else printf("%d\n",depth);
    }    
} 

 

posted @ 2020-06-22 09:17  WA自动机~  阅读(185)  评论(0编辑  收藏  举报