只能说这题的估价函数太神了.............黑书的思想一直很牛X
题意:有一个 1--n的序列,顺序打乱了,目标是要让每个数字在对应的位置(1对应1.......),移动的时候取连续的一段与左边或者右边的某一段交换位置(一段也可以是一个数值)。
分析:摘自黑书:本题和传统的八数码问题有类似之处,但是其启发函数不好找。如果也是把所有段落的曼哈顿距离作为估价值,则h函数是不相容的,一次移动可能让所有段落离家距离减少很多(不是线性的)。
移动是以连续段落为整体的,启发我们设计一个“相对位置”的h函数,例如h = “后继段落正确的段落数目”。显然假设把段落S从P1后面移动到P2后面,则有1.S的最后一个段落 2.
P1 3.P2 三个段落后继有变化。每次最多减少3,。常熟级别了。所以把估价函数扩大三倍f = h + 3 * step 就是线性的了。
这里取的是后继段落中错误的段落数目..............所以h == 0 的时候,找到了解。
#include <iostream> #include <algorithm> #include <cmath> #include <cstdio> #include <cstdlib> #include <cstring> #include <string> #include <vector> #include <set> #include <queue> #include <stack> #include <climits>//形如INT_MAX一类的 #define MAX 100005 #define INF 0x7FFFFFFF #define REP(i,s,t) for(int i=(s);i<=(t);++i) #define ll long long #define mem(a,b) memset(a,b,sizeof(a)) #define mp(a,b) make_pair(a,b) #define L(x) x<<1 #define R(x) x<<1|1 # define eps 1e-5 //#pragma comment(linker, "/STACK:36777216") ///传说中的外挂 using namespace std; int a[20],n,limit; int ans; int h(int a[]) { int v = 0; if (a[1] != 1) v++; for (int i=1; i<n; i++) if (a[i+1] != a[i]+1) v++; return v; } void trans(int *a,int p1,int p2,int p3) { int tmp[20]; for (int i=p1; i<=p2; i++) tmp[i] = a[i]; for (int i=p2+1; i<=p3; i++) a[i-p2+p1-1] = a[i]; for (int i=p1+p3-p2; i<=p3; i++) a[i] = tmp[i+p2-p3]; } int dfs(int *a,int dep) { int hv = h(a); if (hv == 0) { ans = 1; return dep; } if (hv + 3 * dep > limit) return hv + 3 * dep; int minn = 11111; for (int i=1; i<n; i++) for (int j=i+1; j<=n; j++) //交换i,k区间和k,j区间 for (int k=i; k<j; k++) { int tmp[20]; for (int r=1; r<=n; r++) tmp[r] = a[r]; trans(tmp,i,k,j); int t = dfs(tmp,dep+1); if (ans) return t; minn = min(minn,t); } return minn; } void IDA_star() { limit = h(a); ans = 0; while (!ans && limit <= 12) limit = dfs(a,0); if (ans) printf("%d\n",limit); else printf("5 or more\n"); } int main() { int T; scanf("%d",&T); while(T--) { scanf("%d",&n); for (int i=1; i<=n; i++) scanf("%d",&a[i]); if (h(a) == 0) { printf("0\n"); continue; } IDA_star(); } return 0; }