AcWing 180 排书【DFS IDA*】

题目描述:

给定n本书,编号为1-n。

在初始状态下,书是任意排列的。

在每一次操作中,可以抽取其中连续的一段,再把这段插入到其他某个位置。

我们的目标状态是把书按照1-n的顺序依次排列。

求最少需要多少次操作。

输入格式

第一行包含整数T,表示共有T组测试数据。

每组数据包含两行,第一行为整数n,表示书的数量。

第二行为n个整数,表示1-n的一种任意排列。

同行数之间用空格隔开。

输出格式

每组数据输出一个最少操作次数。

如果最少操作次数大于或等于5次,则输出”5 or more”。

每个结果占一行。

数据范围

1≤n≤15

输入样例:

3
6
1 3 4 6 2 5
5
5 4 3 2 1
10
6 8 5 3 4 7 2 9 1 10

输出样例:

2
3
5 or more

分析:此题使用IDA算法进行解决,IDA也就是 迭代加深 + A*算法的结合。
估价函数的选择: 可以发现,我们排书就是为了使得一个序列中的所有元素的后继元素和自己是一个加1的关系,我们记录一下一个序列的所有不正确后继的个数,例如 1 3 4 6 2 5, 1的后继应该是2 , 3 的后继就是 4 ,即此序列所有不正确后继的个数为 4 , 通过尝试移动区间后可以发现,每一次移动最多改变3个后继状态,比如如果将5放置在6的前面,那么4的后继、5的后继、2的后继发生改变。所以我们可以将估价函数设置为错误后继的个数。那么当前错误后继个数为cnt时,至少需要cnt / 3上取整次移动才能将序列恢复为有序。估价函数f就等于(cnt + 2) / 3,这里加上2是为了上取整。

代码书写时,基本上就是迭代加深的套路,然后要注意一个恢复现场,即要利用一个w临时数组,帮助p数组进行区间移动的变换。dfs 参数为当前深度和最大深度,当当前深度u+估价函数f()判断出来的最少步数 都大于了最大深度限制depth,那么直接return false,当f()为0时即序列已经排好了,可以return true。dfs内,枚举区间的长度,再枚举区间的起点,然后枚举每一次可能移动到的位置,这里有个细节,就是只能向后移,因为向前移是重复冗余的。

#include <iostream>
#include <cstring>


using namespace std;

const int N = 20;

int p[N] , n ;

int f(){
    int t = 0;
    
    for(int i = 0 ; i + 1 < n ; i ++)
        if( p[i+1] != p[i] + 1 ) 
            t ++;
    return ( t + 2 ) / 3;
}

bool dfs(int u , int depth ){
    if( u + f() > depth )  return false;
    if( f() == 0 ) return true;
    
    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 ++ )
            {
                int w[N];
                memcpy(w , p , sizeof p);
                
                int x , y;
                for( x = l , y = r + 1 ; y <= k ; x ++ , y ++ ) p[x] = w[y];
                for( y = l ; y <= r ; x++ , y ++ ) p[x] = w[y];
                
                if( dfs(u + 1 , depth ) ) return true;
                
                memcpy(p , w , sizeof w);
            }
        }
    }
    
    return false;
}

int main(){
    int T;
    cin >> T;
    while( T -- )
    {
        cin >> n;
        for(int i = 0 ; i < n ; i ++ ) scanf("%d",&p[i]);
        
        int depth = 0 ;
        while ( depth < 5 && dfs( 0 ,  depth ) == false )  depth ++;
        
        if( depth >= 5 ) puts("5 or more");
        else printf("%d\n",depth);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值