UVa-11212编辑书稿

https://blog.csdn.net/flsjzl/article/details/51785488
题意:
你有一篇n个自然段组成的文章,希望将它们排列成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}前。

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

1)迭代加深搜

2)启发式算法

迭代加深搜:

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

启发式算法:

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

在这里插入图片描述

大家看假如我们要找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 <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 9;

int n,a[N];

bool ans_sort() {
	for( int i=0; i<n-1; ++i ) {
		if( a[i]>=a[i+1] ) return false;
	} 
	return true;
}

// the number of integers with incorrect successor
int h() {
	int cnt = 0;
	for( int i=0; i<n-1; ++i ) {
		if( a[i]+1!=a[i+1] ) ++cnt;
	} 
	if( a[n-1]!=n ) ++cnt;
	return cnt;
} 

bool dfs( int d, int maxd ) {
	
	if( d*3 + h() > maxd*3 ) return false;
	if( ans_sort() ) return true;
	
	int olda[N],b[N];
	memcpy( olda,a,sizeof(olda) );
	
	for( int i=0; i<n; ++i ) { //左光标 
		for( int j=i; j<n; ++j ) { // 右光标 
			//将a中剪去a[i...j]后剩余的两部分合并到数组b 
			int cnt = 0;
			for( int k=0; k<n; ++k )
				if( k<i||k>j ) b[cnt++] = a[k];
			// insert before position k . 将a[i..j]依次插入到 b 中0,1,2...cnt等位置 
			for( int k=0; k<=cnt; ++k ) {
				int c = 0;
				for( int p=0; p<k; ++p ) a[c++] = b[p];  
				for( int p=i; p<=j; ++p ) a[c++] = olda[p];
				for( int p=k; p<cnt; ++p ) a[c++] = b[p];
				
				if( dfs(d+1,maxd) ) return true;
				memcpy( a,olda,sizeof(a) );
			}
		}
	}
	return false;
}

int solve(){
	if( ans_sort() ) return 0;
	int max_ans = 6;
	for( int maxd=1; maxd<max_ans; ++maxd ) {
		if( dfs(0,maxd) ) {
			return maxd;
		}
	}
	return max_ans;
}

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

	return 0;
}



猜你喜欢

转载自blog.csdn.net/CY05627/article/details/87692356
今日推荐