迭代加深-双向DFS-IDAstar

迭代加深

避免搜索过深 但答案在较浅的位置这种情况的发生

AcWing170. 加成序列

满足如下条件的序列X(序列中元素被标号为1、2、3…m)被称为“加成序列”:

1、 X [ 1 ] = 1 X[1]=1 X[1]=1

2、 X [ m ] = n X[m]=n X[m]=n

3、 X [ 1 ] < X [ 2 ] < … < X [ m − 1 ] < X [ m ] X[1]X[1]<X[2]<<X[m1]<X[m]

4、对于每个 k k k 2 ≤ k ≤ m 2≤k≤m 2km)都存在两个整数 i i i j j j 1 ≤ i , j ≤ k − 1 1≤i,j≤k−1 1i,jk1 i i i j j j 可相等),使得 X [ k ] = X [ i ] + X [ j ] X[k]=X[i]+X[j] X[k]=X[i]+X[j]

你的任务是:给定一个整数n,找出符合上述条件的长度m最小的“加成序列”。

如果有多个满足要求的答案,只需要找出任意一个可行解。

输入格式

输入包含多组测试用例。

每组测试用例占据一行,包含一个整数n。

当输入为单行的0时,表示输入结束。

输出格式

对于每个测试用例,输出一个满足需求的整数序列,数字之间用空格隔开。

每个输出占一行。

数据范围

1 ≤ n ≤ 100 1≤n≤100 1n100

剪枝策略

优化搜索顺序:① 优先枚举较大的数

排除等效冗余:① 某两对数和相等时 只需要选择其中一对

代码
#include

using namespace std;
const int N = 110;
int n;
int ans[N];

bool dfs(int u, int depth) {
	if (u > depth)return false;
	if (ans[u - 1] == n)return true;

	bool st[N] = { 0 };
	for (int i = u - 1; i >= 0; --i) {
		for (int j = i; j >= 0; --j) {
			int s = ans[i] + ans[j];
			if (st[s] || s > n || s <= ans[u - 1])continue;
			st[s] = true;
			ans[u] = s;
			if (dfs(u + 1, depth))return true;
		}
	}

	return false;
}
int main() {
	ans[0] = 1;
	while (cin >> n, n) {
		int depth = 1;
		while (!dfs(1, depth))depth++;

		for (int i = 0; i < depth; ++i)printf("%d ", ans[i]);
		cout << endl;
	}
}

双向DFS

AcWing171. 送礼物

达达帮翰翰给女生送礼物,翰翰一共准备了 N N N个礼物,其中第 i i i个礼物的重量是 G [ i ] G[i] G[i]

达达的力气很大,他一次可以搬动重量之和不超过 W W W的任意多个物品。

达达希望一次搬掉尽量重的一些物品,请你告诉达达在他的力气范围内一次性能搬动的最大重量是多少。

输入格式

第一行两个整数,分别代表W和N。

以后N行,每行一个正整数表示G[i]。

输出格式

仅一个整数,表示达达在他的力气范围内一次性能搬动的最大重量。

数据范围

1 ≤ N ≤ 46 1≤N≤46 1N46,
1 ≤ W , G [ i ] ≤ 2 31 − 1 1≤W,G[i]≤2^{31}−1 1W,G[i]2311

剪枝策略

1.将所有物品按重量从大到小排序

2.先将前 k k k件物品能凑出的重量打表,然后排序判重

3.搜索剩下的 N − K N-K NK 件物品的选择方式 并从表中二分出不超过 W W W 的最大值

代码
#include

using namespace std;
typedef long long LL;
const int N = 50;
int n, m;
int w[N], weights[1 << 25];
int k, cnt = 1, res;

void dfs1(int u, int s) {
	if (u == k) {
		weights[cnt++] = s;
		return;
	}

	dfs1(u + 1, s);
	if ((LL)s + w[u] <= m)dfs1(u + 1, s + w[u]);
}

void dfs2(int u, int s) {
	if (u == n) {
		int l = 0, r = n;
		while (l < r) {
			int mid = l + r + 1 >> 1;
			if (weights[mid] <= m - s)l = mid;
			else r = mid - 1;
		}
		res = max(res, weights[l] + s);
		return;
	}

	dfs2(u + 1, s);
	if ((LL)s + w[u] <= m)dfs2(u + 1, s + w[u]);
}
int main() {
	cin >> m >> n;
	for(int i = 1;i <= n;++i){
		scanf("%d", &w[i]);
	}
	sort(w, w + n);
	reverse(w, w + n);

	int k = n / 2 + 2;
	
	dfs1(1, 0);
	cnt = unique(weights, weights + cnt) - weights;

	dfs2(k + 1, 0);
	cout << res << endl;

	return 0;
}

IDA-star

启发式搜索 + 迭代加深

如果估价函数(需要再搜的最小层数) + 当前层数 > 当前迭代加深的层数 在当前迭代肯定无解

迭代扩大搜索的层数 直到最终找到答案 或者无解

AcWing180. 排书

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

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

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

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

求最少需要多少次操作。

输入格式

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

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

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

同行数之间用空格隔开。

输出格式

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

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

每个结果占一行。

数据范围

1 ≤ n ≤ 15 1≤n≤15 1n15

思路

枚举移动串的长度 和插入的位置 一层一层地搜

代码
#include

using namespace std;
typedef long long LL;
const int N = 15;
int n;
int a[N], w[5][N];

int f() {
	int cnt = 0;
	for (int i = 0; i + 1 < n; ++i)
		if (a[i + 1] != a[i] + 1)
			++cnt;

	return (cnt + 2) / 3;
}
bool dfs(int depth, int max_depth) {
	if (depth + f() > max_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; //计算终点
			// 把 l 到 r 这一段放到k的后面去
			for (int k = r + 1; k < n; ++k) {// 枚举应该插在哪个地方 避免重复 每次都往后放
				memcpy(w[depth], a, sizeof a);
				int y = l;
				for (int x = r + 1; x <= k; ++x,++y)a[y] = w[depth][x];
				for (int x = l; x <= r; ++x, ++y)a[y] = w[depth][x];
				if (dfs(depth + 1, max_depth))return true;
				memcpy(a, w[depth], sizeof a);
			}
		}
	}

	return false;
}
int main() {
	int t; cin >> t;
	while (t--) {
		cin >> n;
		for (int i = 0; i < n; ++i)scanf("%d", &a[i]);

		int depth = 0;
		while (depth < 5 && !dfs(0, depth))depth++;

		if (depth >= 5)puts("5 or more");
		else cout << depth << endl;
	}
}

AcWing181. 回转游戏

如下图所示,有一个“#”形的棋盘,上面有1,2,3三种数字各8个。

给定8种操作,分别为图中的A~H。

这些操作会按照图中字母和箭头所指明的方向,把一条长为8的序列循环移动1个单位。

例如下图最左边的“#”形棋盘执行操作A后,会变为下图中间的“#”形棋盘,再执行操作C后会变成下图最右边的“#”形棋盘。

给定一个初始状态,请使用最少的操作次数,使“#”形棋盘最中间的8个格子里的数字相同。

image-20201120232807594

输入格式

输入包含多组测试用例。

每个测试用例占一行,包含24个数字,表示将初始棋盘中的每一个位置的数字,按整体从上到下,同行从左到右的顺序依次列出。

输入样例中的第一个测试用例,对应上图最左边棋盘的初始状态。

当输入只包含一个“0”的行时,表示输入终止。

输出格式

每个测试用例输出占两行。

第一行包含所有移动步骤,每步移动用大写字母“A~G”中的一个表示,字母之间没有空格,如果不需要移动则输出“No moves needed”。

第二行包含一个整数,表示移动完成后,中间8个格子里的数字。

如果有多种方案,则输出字典序最小的解决方案。

代码
#include

using namespace std;
typedef long long LL;
const int N = 24;

int op[8][7] = { //图表顺序
	{0,2,6,11,15,20,22},
	{1,3,8,12,17,21,23},
	{10,9,8,7,6,5,4},
	{19,18,17,16,15,14,13},
	{23,21,17,12,8,3,1},
	{22,20,15,11,6,2,0},
	{13,14,15,16,17,18,19},
	{4,5,6,7,8,9,10},
};
int opposite[8] = { 5,4,7,6,1,0,3,2 }; //每个操作对应的逆操作
int center[8] = { 6,7,8,11,12,15,16,17 };
int q[N];
int path[100];

int f() {
	int sum[4] = { 0 };
	for (int i = 0; i < 8; ++i) {
		sum[q[center[i]]]++;
	}
	int s = 0;
	for (int i = 1; i <= 3; ++i) {
		s = max(s, sum[i]);
	}
	return 8 - s;
}

void operate(int x) {
	int s = q[op[x][0]];
	for (int i = 0; i < 6; ++i) {
		q[op[x][i]] = q[op[x][i + 1]];
	}
	q[op[x][6]] = s;
}

bool dfs(int depth, int max_depth,int last) {
	if (depth + f() > max_depth)return false;
	if (!f())return true;

	for (int i = 0; i < 8; ++i) {
		if (opposite[i] != last) {
			operate(i);
			path[depth] = i;
			if (dfs(depth + 1, max_depth,i))return true;
			operate(opposite[i]);
		}
	}

	return false;
}
int main() {
	
	while (cin >> q[0], q[0]) {
		for (int i = 1; i < 24; ++i)scanf("%d", &q[i]);

		int depth = 0;
		while (!dfs(0, depth,-1))depth++;

		if (!depth)printf("No moves needed");
		else {
			for (int i = 0; i < depth; ++i)printf("%c", path[i] + 'A');
		}
		printf("\n%d\n", q[6]);
	}
	
}

你可能感兴趣的:(搜索,算法,c++,搜索,迭代加深,DFS)