IDA*

181. 回转游戏

题目链接:回转游戏

算法分析:
要保证估价函数小于等于未来实际值,只能以最理性状态分析。对于每个状态的中间8个格子,假设出现次数最多的数字为k,其余非k的数字要变成k,最少需要操作8 - k次。未来实际值不会少于这个次数。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int b[8][8], d[25], szmove[1000010];
int anstep, ansval;
inline int g()
{
	int f[4];
	memset(f, 0, sizeof(f));
	++f[b[3][3]]; ++f[b[3][4]]; ++f[b[3][5]];
	++f[b[4][3]];               ++f[b[4][5]];
	++f[b[5][3]]; ++f[b[5][4]]; ++f[b[5][5]];
	
	if (f[1] > f[2] && f[1] > f[3]) return 8 - f[1];
	if (f[2] > f[1] && f[2] > f[3]) return 8 - f[2];
	if (f[3] > f[1] && f[3] > f[2]) return 8 - f[3];
}

inline bool isok()
{
	if (b[3][3] == b[3][4] && b[3][4] == b[3][5] && b[3][5] == b[4][3] && b[4][3] == b[4][5] && b[4][5] == b[5][3] && b[5][3] == b[5][4] && b[5][4] == b[5][5]) return 1;
	return 0;
}
bool dfs(int step, int maxdep, int dir) // dir:上一步的操作编号 
{
//	cout<<"step="<<step<<endl;
//	cout<<g()<<endl;
	if (step + g() > maxdep) return 0;
	
	if (isok()) 
	{
		anstep = step;
		ansval = b[3][3];
		return 1;
	}

	// A 
	if (dir != 6)
	{
		int tem[8][8];
		memcpy(tem, b, sizeof(b));
		int t = b[1][3];
		for (int i = 2; i <= 7; ++i) b[i-1][3] = b[i][3];
		b[7][3] = t;
		szmove[step+1] = 1;
		if (dfs(step + 1, maxdep, 1)) return 1;
		memcpy(b, tem, sizeof(b));
	}
	
	// B
	if (dir != 5)
	{
		int tem[8][8];
		memcpy(tem, b, sizeof(b));
		int t = b[1][5];
		for (int i = 2; i <= 7; ++i) b[i-1][5] = b[i][5];
		b[7][5] = t;
		szmove[step+1] = 2;
		if (dfs(step + 1, maxdep, 2)) return 1;
		memcpy(b, tem, sizeof(b));
	}
	
	// C
	if (dir != 8)
	{
		int tem[8][8];
		memcpy(tem, b, sizeof(b));
		int t = b[3][7];
		for (int j = 6; j >= 1; --j) b[3][j+1] = b[3][j];
		b[3][1] = t;
		szmove[step+1] = 3;
		if (dfs(step + 1, maxdep, 3)) return 1;
		memcpy(b, tem, sizeof(b));
	}
	
	// D
	if (dir != 7)
	{
		int tem[8][8];
		memcpy(tem, b, sizeof(b));
		int t = b[5][7];
		for (int j = 6; j >= 1; --j) b[5][j+1] = b[5][j];
		b[5][1] = t;
		szmove[step+1] = 4;
		if (dfs(step + 1, maxdep, 4)) return 1;
		memcpy(b, tem, sizeof(b));
	}
	
	// E
	if (dir != 2)
	{
		int tem[8][8];
		memcpy(tem, b, sizeof(b));
		int t = b[7][5];
		for (int i = 6; i >= 1; --i) b[i+1][5] = b[i][5];
		b[1][5] = t;
		szmove[step+1] = 5;
		if (dfs(step + 1, maxdep, 5)) return 1;
		memcpy(b, tem, sizeof(b));
	}
	
	// F
	if (dir != 1)
	{
		int tem[8][8];
		memcpy(tem, b, sizeof(b));
		int t = b[7][3];
		for (int i = 6; i >= 1; --i) b[i+1][3] = b[i][3];
		b[1][3] = t;
		szmove[step+1] = 6;
		if (dfs(step + 1, maxdep, 6)) return 1;
		memcpy(b, tem, sizeof(b));
	}
	
	// G
	if (dir != 4)
	{
		int tem[8][8];
		memcpy(tem, b, sizeof(b));
		int t = b[5][1];
		for (int j = 2; j <= 7; ++j) b[5][j-1] = b[5][j];
		b[5][7] = t;
		szmove[step+1] = 7;
		if (dfs(step + 1, maxdep, 7)) return 1;
		memcpy(b, tem, sizeof(b));
	}
	
	// H
	if (dir != 3)
	{
		int tem[8][8];
		memcpy(tem, b, sizeof(b));
		int t = b[3][1];
		for (int j = 2; j <= 7; ++j) b[3][j-1] = b[3][j];
		b[3][7] = t;
		szmove[step+1] = 8;
		if (dfs(step + 1, maxdep, 8)) return 1;
		memcpy(b, tem, sizeof(b));
	}
	
	return 0;
} 
int main()
{
	while (1)
	{
		scanf("%d", &d[1]);
		if (!d[1]) break;
		for (int i = 2; i <= 24; ++i) scanf("%d", &d[i]);
		
		b[1][3] = d[1];
		b[1][5] = d[2];
		b[2][3] = d[3];
		b[2][5] = d[4];
		b[3][1] = d[5];
		b[3][2] = d[6];
		b[3][3] = d[7];
		b[3][4] = d[8];
		b[3][5] = d[9];
		b[3][6] = d[10];
		b[3][7] = d[11];
		b[4][3] = d[12];
		b[4][5] = d[13];
		b[5][1] = d[14];
		b[5][2] = d[15];
		b[5][3] = d[16];
		b[5][4] = d[17];
		b[5][5] = d[18];
		b[5][6] = d[19];
		b[5][7] = d[20];
		b[6][3] = d[21];
		b[6][5] = d[22];
		b[7][3] = d[23];
		b[7][5] = d[24];
		
		int maxdep = 0;
	//	szmove[0] = 0;
		anstep = 0;
		while (1)
		{
			if (dfs(0, maxdep, 0))
			{
				if (anstep == 0) printf("No moves needed\n");
				else
				{
					for (int i = 1; i <= anstep; ++i)
					{
						if (szmove[i] == 1) printf("A");
						if (szmove[i] == 2) printf("B");
						if (szmove[i] == 3) printf("C");
						if (szmove[i] == 4) printf("D");
						if (szmove[i] == 5) printf("E");
						if (szmove[i] == 6) printf("F");
						if (szmove[i] == 7) printf("G");
						if (szmove[i] == 8) printf("H");
					}
					printf("\n");
				}
				printf("%d\n", ansval);
				break;
			}	
			++maxdep;
		}
		
	}
	return 0;
}

总结与反思:
1.数组b不能直接当做dfs的参数传递,因为数组是传址调用,会在后续的递归操作中改变b的值。因此,每次赋值给tem,操作后再反赋值回来给b,不会造成b发生变化。

2.迭代加深因为有步数限制,所以,可以不用标记,因为步数很浅。如果步数深,必须得用标记了。但步数深,也就不用迭代加深了。

180. 排书

题目链接:180. 排书

算法分析:
难点还是估价函数。在既定状态下,假设第i本书后是第i + 1本书,称i + 1是i的正确后继。每个状态下,假设所有书的错误后继总数为tot。每操作一次,最多改变3本书的错误后继,理想状态就是这3本书的错误后继都能修正。这样,该状态最少需要操作(tot + 2) / 3次。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <ctime>
using namespace std;
#define ull unsigned long long 
const int p = 2887777;
int d, n, b[20];
ull szfirst[2900000], szdata[1000000], sznext[1000000], cnt;
inline bool isok(int tem[])
{
	for (int i = 1; i < n; ++i) if (tem[i] + 1 != tem[i+1]) return 0;
	return 1;
}
inline void biaoji(int b[])
{
	ull sum = 0;
	for (int i = 1; i <= n; ++i) sum = sum * 16 + b[i];
	
	int u = sum % p;
	++cnt;
	
	szdata[cnt] = sum;
	sznext[cnt] = szfirst[u];
	szfirst[u] = cnt;
}
inline int fnd(int b[])
{
	ull sum = 0;
	for (int i = 1; i <= n; ++i) sum = sum * 16 + b[i];
	
	int u = sum % p;
	int d = szfirst[u];
	
	while (d)
	{
		if (szdata[d] == sum) return 1;
		d = sznext[d];
	}
	return 0;
}
inline void szdel(int b[])
{
	ull sum = 0;
	for (int i = 1; i <= n; ++i) sum = sum * 16 + b[i];
	
	int u = sum % p;	
	int d = szfirst[u];
	
	if (szdata[d] == sum)
	{
		szfirst[u] = sznext[d]; return;
	}
	
	int pre = d;
	while (d)
	{
		if (szdata[d] == sum) break;
		pre = d;
		d = sznext[d];
	}
	
	sznext[pre] = sznext[d];  // 删掉编号为d的结点  
	
}
bool dfs(int b[], int dep)
{
	//统计错误后继个数 
	int tot = 0; 
	for (int i = 1; i < n; ++i) if (b[i] + 1 != b[i+1]) ++tot;  
	if (dep - 1 + ceil(tot / 3.0) > d) return 0;
//	cout<<"dep-1……="<<dep - 1 + ceil(tot / 3.0)<<" d="<<d<<endl;

	
	if(isok(b))
	{
	//	for (int i = 1; i <= n; ++i) cout<<b[i]<<" "; cout<<endl;
	//	cout<<"dep="<<dep<<endl;
	//	getchar(); getchar();
		return 1;
	}
	
	for (int len = 1; len < n; ++len)  // 枚举将要移动的区间长度 
		for (int i = 1; i <= n; ++i)	// 枚举区间左端点 
		{
			int j = i + len - 1;  // 区间右端点   
			if (j > n) continue; 
			
			int bb[20], tem[20];
			int s = 0;
			for (int k = 1; k <= i -1; ++k) bb[++s] = b[k];
			for (int k = j + 1; k <= n; ++k) bb[++s] = b[k];
			
		//	cout<<"dep="<<dep<<" d="<<d<<" len="<<len<<" i="<<i<<" j="<<j<<endl;
		//	getchar(); getchar();
		
			
			
			// 枚举插入点 :插在最前面 	 
			s = 0;
			for (int k = i; k <= j; ++k) tem[++s] = b[k];
			for (int k = 1; k <= n - (j - i) -1; ++k) tem[++s] = bb[k];	
			
		//	cout<<"i到j:";for (int k = i; k <= j; ++k) cout<<b[k]<<" "; cout<<endl;
		//	for (int k = 1; k <= n; ++k) cout<<tem[k]<<" "; cout<<endl;
			
		//	if (dep == 1 && d == 2 && i == 3 && j == 5)
		//	{
		//		getchar(); getchar();
		//	}
			
			/*
			if (b[1] == 2 && b[2] == 1 && b[3] == 3 && b[4] == 4 && b[5] == 5)
			{
				cout<<"2 1 3 4 5"<<endl;
				cout<<"dep="<<dep<<endl;
				getchar(); getchar();
			}
			*/
			if (!fnd(tem)) 
			{
				biaoji(tem);
				if (dfs(tem, dep + 1)) return 1;
				szdel(tem);
			}
			
			// 枚举其他插入点  
			for (int t = 1; t <= n - (j - i) -1; ++t)  // 插到每个点的后面  
			{
				 s = 0;
				 for (int k = 1; k <= t; ++k) tem[++s] = bb[k];
				 for (int k = i; k <= j; ++k) tem[++s] = b[k];
				 for (int k = t + 1; k <= n - (j - i) -1; ++k) tem[++s] = bb[k];
				 
				 if (!fnd(tem))
				 {
				 	biaoji(tem);
					if (dfs(tem, dep + 1)) return 1;
					szdel(tem);
				 }
				 	
			} 
			
		} 
	return 0;
}
int main()  // 过三个点,其余点超时  
{
	double st = clock();
	int T;
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d", &n);
		for (int i = 1; i <= n; ++i) scanf("%d", &b[i]);
		
		if (isok(b))
		{
			printf("0\n"); continue;
		}		
		d = 0;
		while (1)
		{
			++d;  // d是搜索深度  
			cnt = 0;
			memset(szfirst, 0, sizeof(szfirst));
		//	memset(sznext, 0, sizeof(sznext));
		//	memset(szdata, 0, sizeof(szdata));
			biaoji(b);
			if (dfs(b, 1) || d >= 5) break;
			szdel(b);
		}
		if (d < 5) printf("%d\n", d);
		else printf("5 or more\n");
	}
	double ed = clock();
	printf("%.0lf\n", ed - st); 
	return 0;
}

总结与反思:
1.本来想着进行标记,状态也复杂,就想用哈希表。结果发现,标记用时比不标记还长,得不偿失。大段代码都是标记,没派上用场。
2.选取某一段进行随机插入是操作难点,一不小心就会超时。由于运动是相对的,因此,只要将某一段向后插入即可。代码中的实现比较精妙。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值