1. 程式人生 > >noip模擬賽(10.4) 序列(sequence)

noip模擬賽(10.4) 序列(sequence)

序列(sequence)

【題目描述】

給定一個1~n的排列x,每次你可以將x1~xi翻轉。你需要求出將序列變為升序的最小操作次數。有多組資料。

【輸入資料】

第一行一個整數t表示資料組數。

每組資料第一行一個整數n,第二行n個整數x1~xn。

【輸出資料】

      每組資料輸出一行一個整數表示答案。

【樣例輸入】

1

8

8 6 1 3 2 4 5 7

【樣例輸出】

7

【資料範圍】

對於100%的測試資料,t=5,n<=25。

對於測試點1,2,n=5。

對於測試點3,4,n=6。

對於測試點5,6,n=7。

對於測試點7,8,9,n=8。

對於測試點10,n=9。

對於測試點11,n=10。

對於測試點i (12<=i<=25),n=i。

【題解】

正解:啟發式搜素+迭代

即,在暴力的迭代搜尋基礎上,加上估價函式。

估價函式的對映規則是這樣的:因為每次翻轉後區間內相鄰的數字出現了變化 的都是1和第i個數,當第i個數與相鄰的後一個數之間差值正好為1時,這次交換導致求解的期望次數一定會+1,由此得到當目前的步數+期望步數>迭代限制時,剪枝。

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
const int N=50;
inline int read()
{
	int x=0,c=getchar(),f=1;
	while(c<48||c>57){if(c=='-')f=-1;c=getchar();}
	while(c>47&&c<58)x=x*10+c-48,c=getchar();
	return x*f;
}
bool flag;
int n,a[N],m;
inline void rev(int i)
{
	for(int j=1;j<=(i>>1);j++)
		swap(a[j],a[i-j+1]);
}
inline void dfs(int i,int k)
{
	int j,l;
	if(i+k>m)return;
	for(j=1;j<=n;j++)
		if(a[j]^j)
			break;
	if(j>n){
		flag=1;
		return;
	}
	for(j=2;j<=n;j++){
		l=k+(j<n && abs(a[j]-a[j+1])==1)-(j<n && abs(a[1]-a[j+1])==1);
		rev(j);
		dfs(i+1,l);
		rev(j);
		if(flag)
			return;
	}
}
int main()
{
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	int i,k,T=read();
	while(T--){
		n=read();
		for(i=1;i<=n;i++)
			a[i]=read();
		for(i=1,k=0;i<=n-1;i++)
			if(abs(a[i]-a[i+1])>1)
				k++;
		for(m=0;;m++){
			flag=0;
			dfs(0,k);
			if(flag)
				break;
		}
		printf("%d\n",m);
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}