算法刷题day55:搜索(二)

引言

上篇文章主要是讲 B F S BFS BFS 的,主要应用在搜索中找最短路方面,主要就是在内部搜索,和整体搜索。而 D F S DFS DFS 其实就是暴力,主要介绍如果写出暴力,并且写出优化与剪枝,有时候能用 B F S BFS BFS ,就用它,因为不容易失误,但是用 D F S DFS DFS 就是不会的时候用,好处就是可以剪枝。那就还是以题目的方式,进行讲解。


一、迷宫

标签:DFS

思路:其实这道题用 B F S BFS BFS 就是最保险的,因为就是一个基本的最短路,但因为本章节是讲 D F S DFS DFS 的,所以就拿 D F S DFS DFS 写了,也顺便粘一个 B F S BFS BFS 的吧,没什么说的,详情见代码。

题目描述:

一天Extense在森林里探险的时候不小心走入了一个迷宫,迷宫可以看成是由 n∗n 的格点组成,每个格点只有2种状态,.
和#,前者表示可以通行后者表示不能通行。

同时当Extense处在某个格点时,他只能移动到东南西北(或者说上下左右)四个方向之一的相邻格点上,Extense想要从点A走到
点B,问在不走出迷宫的情况下能不能办到。

如果起点或者终点有一个不能通行(为#),则看成无法办到。

注意:A、B不一定是两个不同的点。

输入格式
第1行是测试数据的组数 k,后面跟着 k 组输入。

每组测试数据的第1行是一个正整数 n,表示迷宫的规模是 n∗n 的。

接下来是一个 n∗n 的矩阵,矩阵中的元素为.或者#。

再接下来一行是 4 个整数 ha,la,hb,lb,描述 A 处在第 ha 行, 第 la 列,B 处在第 hb 行, 第 lb 列。

注意到 ha,la,hb,lb 全部是从 0 开始计数的。

输出格式
k行,每行输出对应一个输入。

能办到则输出“YES”,否则输出“NO”。

数据范围
1≤n≤100
输入样例:
2
3
.##
..#
#..
0 0 2 2
5
.....
###.#
..#..
###..
...#.
0 0 4 0
输出样例:
YES
NO

示例代码1:DFS版

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 110, M = N, INF = 0x3f3f3f3f;

int n, m;
char g[N][N];
bool st[N][N];
PII S, E;

int dir[4][2] = {0,1,0,-1,1,0,-1,0};

bool dfs(int x, int y)
{
	st[x][y] = true;
	if(x == E.x && y == E.y) return true;
	
	for(int i = 0; i < 4; ++i)
	{
		int a = x + dir[i][0];
		int b = y + dir[i][1];
		if(a < 0 || a >= n || b < 0 || b >= n) continue;
		if(st[a][b] || g[a][b] == '#') continue;
		
		if(dfs(a,b)) return true;
	}
	
	return false;
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	int T; cin >> T;
	while(T--)
	{
		memset(st, 0, sizeof st);
		cin >> n;
		for(int i = 0; i < n; ++i) cin >> g[i];
		cin >> S.x >> S.y >> E.x >> E.y;
		
		bool flag;
		if(g[S.x][S.y] == '#' || g[E.x][E.y] == '#') flag = false;
		else flag = dfs(S.x,S.y);
		
		if(flag) cout << "YES" << endl;
		else cout << "NO" << endl;
	}
	
	return 0;
}

示例代码2:BFS

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 110, M = N, INF = 0x3f3f3f3f;

int n, m;
char g[N][N];
bool st[N][N];
PII S, E;

int dir[4][2] = {0,1,0,-1,1,0,-1,0};

bool bfs()
{
	if(g[S.x][S.y] == '#' || g[E.x][E.y] == '#') return false;  // 没说,起始点可能就是死路
	memset(st, 0, sizeof st);
	st[S.x][S.y] = true;
	
	queue<PII> q; q.push(S);
	while(q.size())
	{
		auto t = q.front(); q.pop();
		if(t == E) return true;  // 写这里而不是下面,防止起点即终点
		
		for(int i = 0; i < 4; ++i)
		{
			int x = t.x + dir[i][0];
			int y = t.y + dir[i][1];
			if(x < 0 || x >= n || y < 0 || y >= n) continue;
			if(st[x][y] || g[x][y] == '#') continue;
			
			st[x][y] = true;
			q.push({x,y});
		}
	}
	
	return false;
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	int T; cin >> T;
	while(T--)
	{
		cin >> n;
		for(int i = 0; i < n; ++i) cin >> g[i];
		cin >> S.x >> S.y >> E.x >> E.y;
		
		if(bfs()) cout << "YES" << endl;
		else cout << "NO" << endl;
	}
	
	return 0;
}

二、红与黑

标签:DFS、Flood Fill

思路:其实就是找从起点开始能走多少个点,也没啥说的,两个版本都在下面,看看即可。

题目描述:

有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。

你站在其中一块黑色的瓷砖上,只能向相邻(上下左右四个方向)的黑色瓷砖移动。

请写一个程序,计算你总共能够到达多少块黑色的瓷砖。

输入格式
输入包括多个数据集合。

每个数据集合的第一行是两个整数 W 和 H,分别表示 x 方向和 y 方向瓷砖的数量。

在接下来的 H 行中,每行包括 W 个字符。每个字符表示一块瓷砖的颜色,规则如下

1)‘.’:黑色的瓷砖;
2)‘#’:红色的瓷砖;
3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。

当在一行中读入的是两个零时,表示输入结束。

输出格式
对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。

数据范围
1≤W,H≤20
输入样例:
6 9 
....#. 
.....# 
...... 
...... 
...... 
...... 
...... 
#@...# 
.#..#. 
0 0
输出样例:
45

示例代码1:DFS

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 25, M = N, INF = 0x3f3f3f3f;

int n, m;
char g[N][N];
bool st[N][N];
PII S;

int dir[4][2] = {0,1,0,-1,1,0,-1,0};

int dfs(int x, int y)
{
    if(g[x][y] == '#') return 0;  // 防止起始点为障碍
	st[x][y] = true;
	int res = 1;
	
	for(int i = 0; i < 4; ++i)
	{
		int a = x + dir[i][0];
		int b = y + dir[i][1];
		if(a < 0 || a >= n || b < 0 || b >= m) continue;
		if(st[a][b] || g[a][b] == '#') continue;
		
		res += dfs(a,b);
	}
	
	return res;
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	while(cin >> m >> n, n || m)
	{
	    memset(st, 0, sizeof st);
		for(int i = 0; i < n; ++i)
		{
			cin >> g[i];
			for(int j = 0; j < m; ++j)
			{
				if(g[i][j] == '@') S = {i,j};
			}
		}
		
		cout << dfs(S.x,S.y) << endl;
	}
	
	return 0;
}

示例代码2:BFS

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 25, M = N, INF = 0x3f3f3f3f;

int n, m;
char g[N][N];
bool st[N][N];
PII S;

int dir[4][2] = {0,1,0,-1,1,0,-1,0};

int bfs()
{
	memset(st, 0, sizeof st);
	st[S.x][S.y] = true;
	
	int res = 1;
	queue<PII> q; q.push(S);
	while(q.size())
	{
		auto t = q.front(); q.pop();
		
		for(int i = 0; i < 4; ++i)
		{
			int x = t.x + dir[i][0];
			int y = t.y + dir[i][1];
			if(x < 0 || x >= n || y < 0 || y >= m) continue;
			if(st[x][y] || g[x][y] == '#') continue;  // 这里写='#' 是因为起点或者终点是有符号的,而不是!='.' 
			
			res++;
			st[x][y] = true;
			q.push({x,y});
		}
	}
	return res;
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	while(cin >> m >> n, n || m) // 看清楚行列顺序 
	{
		for(int i = 0; i < n; ++i) 
		{
			cin >> g[i];
			for(int j = 0; j < m; ++j)
			{
				if(g[i][j] == '@') S = {i,j};
			}
		}
		
		cout << bfs() << endl;
	}
	
	return 0;
}

三、马走日

标签:DFS

思路:整体思想就是每一个点走的八个方向都是一种方案,只要在遍历过程中能把所有点都给走满,就是一种方案。所以可以定义一个全局变量来记录,然后传一个当前走过点的数量,用来判断是否走满了,然后就是正常的写法了,当然也可以让其返回每个方向上的数量,不用全局变量,也是可以的,这里就不写了。

题目描述:

马在中国象棋以日字形规则移动。

请编写一段程序,给定 n∗m 大小的棋盘,以及马的初始位置 (x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途
径遍历棋盘上的所有点。

输入格式
第一行为整数 T,表示测试数据组数。

每一组测试数据包含一行,为四个整数,分别为棋盘的大小以及初始位置坐标 n,m,x,y。

输出格式
每组测试数据包含一行,为一个整数,表示马能遍历棋盘的途径总数,若无法遍历棋盘上的所有点则输出 0。

数据范围
1≤T≤9,1≤m,n≤9,1≤n×m≤28,0≤x≤n−1,0≤y≤m−1
输入样例:
1
5 4 0 0
输出样例:
32

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 10, M = N, INF = 0x3f3f3f3f;

int n, m;
bool st[N][N];
PII S;
int ans;

int dir[8][2] = {-2,-1, -2,1, -1,-2, -1,2, 1,-2, 1,2, 2,-1, 2,1};

void dfs(int cur, int x, int y)  // 当前正在访问第cur个合法状态{x,y} 
{
	if(cur == n * m) 
	{
		ans++;
		return;
	}
	
	st[x][y] = true;
	for(int i = 0; i < 8; ++i)  // 每一种方向代表一种方案 
	{
		int a = x + dir[i][0];
		int b = y + dir[i][1];
		if(a < 0 || a >= n || b < 0 || b >= m) continue;
		if(st[a][b]) continue;
		
		dfs(cur+1,a,b);
	}
	
	st[x][y] = false;
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	int T; cin >> T;
	while(T--)
	{
		memset(st, 0, sizeof st);
		ans = 0;
		
		cin >> n >> m >> S.x >> S.y;
		
		dfs(1,S.x,S.y);
		
		cout << ans << endl;
	}
	
	return 0;
}

四、单词接龙

标签:DFS

思路:这道题就是传入一个单词,然后遍历自己的子串长度,再遍历单词组,查看是否满足要求,如果满足就接上去,然后定义一个全局变量找最大龙的长度。另外开头为一个字母,需要特判一下。

题目描述:

单词接龙是一个与我们经常玩的成语接龙相类似的游戏。

现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”,每个单词最多被使用两次。

在两个单词相连时,其重合部分合为一部分,例如 beast 和 astonish ,如果接成一条龙则变为 beastonish。

我们可以任意选择重合部分的长度,但其长度必须大于等于1,且严格小于两个串的长度,例如 at 和 atide 间不能相连。

输入格式
输入的第一行为一个单独的整数 n 表示单词数,以下 n 行每行有一个单词(只含有大写或小写字母,长度不超过20),输入的最
后一行为一个单个字符,表示“龙”开头的字母。

你可以假定以此字母开头的“龙”一定存在。

输出格式
只需输出以此字母开头的最长的“龙”的长度。

数据范围
n≤20,单词随机生成。

输入样例:
5
at
touch
cheat
choose
tact
a
输出样例:
23
提示
连成的“龙”为 atoucheatactactouchoose。

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 25, M = N, INF = 0x3f3f3f3f;

int n, m;
string str[N], src;
unordered_map<string,int> cnt;
int ans;

void dfs(string dragon, bool flag)
{
	ans = max(ans, (int)dragon.size());
	
	for(int len = 1; len <= dragon.size(); ++len)
	{
		int size1 = dragon.size();
		if(!flag && len == size1) continue;
		string t1 = dragon.substr(size1-len);
		for(int i = 0; i < n; ++i)
		{
			if(cnt[str[i]] >= 2) continue;
			int size2 = str[i].size();
			if(size2 < 1 || len > size2) continue;
			
			string t2 = str[i].substr(0,len);
			if(t1 != t2) continue;
			cnt[str[i]]++;
			dfs(dragon+str[i].substr(len),false);
			cnt[str[i]]--;
		}
	}
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	cin >> n;
	for(int i = 0; i < n; ++i) cin >> str[i], cnt[str[i]] = 0;
	cin >> src;
	
	dfs(src,true);
	
	cout << ans << endl;
	
	return 0;
}

五、分成互质组

标签:DFS

思路:首先互质是两个数的最大公约数为 1 1 1 ,互质组就是任意两个数的最大公约数都为 1 1 1 。这道题就是一个简单的暴搜,一个数要么加入到当前的组里去,要么新开一个组存进去。这里说一下写法,第一种就是朴素的 void dfs(int u, int k) ,这里的 u , k u,k u,k 都是从 0 0 0 开始的,当 u = n u = n u=n 时,说明当前所有的数都已经被放到组里去了,说明当前的 k k k 是一种方案。这里的 k k k 就是组数,我们放数的时候是从 0 ∼ k − 1 0\sim k-1 0k1 枚举的,这样也符合一般的习惯,新开的数组编号就为 k k k ,当前的组数就变为了 k + 1 k+1 k+1 。然后迭代加深的参数列表为 void dfs(int u, int k, int depth) 从开始的求最值变为了求是否是可行解,因为本就是从最小的值变过来的,只要可行就是最优解。具体细节见代码。

题目描述:

示例代码1:朴素暴搜

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 15, M = N, INF = 0x3f3f3f3f;

int n, m;
int a[N];
vector<int> g[N];
int ans = N;

int gcd(int a, int b)
{
	return b ? gcd(b,a%b) : a;
}

bool check(int x, int k)
{
	for(int i = 0; i < g[k].size(); ++i)
	{
		if(gcd(x,g[k][i]) > 1) return false;
	}
	return true;
}

void dfs(int u, int k)
{
	if(k >= ans) return;
	if(u == n)
	{
		ans = k;
		return;
	}
	
	for(int i = 0; i < k; ++i)
	{
		if(check(a[u],i))
		{
			g[i].push_back(a[u]);
			dfs(u+1,k);
			g[i].pop_back();
		}
	}
	
	g[k].push_back(a[u]);
	dfs(u+1,k+1);
	g[k].pop_back();
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	cin >> n;
	for(int i = 0; i < n; ++i) cin >> a[i];
	
	dfs(0,0);
	
	cout << ans << endl;
	
	return 0;
}

示例代码2:迭代加深

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 15, M = N, INF = 0x3f3f3f3f;

int n, m;
int a[N];
vector<int> g[N];

int gcd(int a, int b)
{
	return b ? gcd(b,a%b) : a;
}

bool check(int x, int k)
{
	for(int i = 0; i < g[k].size(); ++i)
	{
		if(gcd(x,g[k][i]) > 1) return false;
	}
	return true;
}

bool dfs(int u, int k, int depth)
{
	if(k > depth) return false;
	if(u == n) return true;
	
	for(int i = 0; i < k; ++i)
	{
		if(check(a[u],i))
		{
			g[i].push_back(a[u]);
			if(dfs(u+1,k,depth)) return true;
			g[i].pop_back();
		}
	}
	
	g[k].push_back(a[u]);
	if(dfs(u+1,k+1,depth)) return true;
	g[k].pop_back();
	
	return false;
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	cin >> n;
	for(int i = 0; i < n; ++i) cin >> a[i];
	
	int depth = 0;
	while(!dfs(0,0,depth)) depth++;
	
	cout << depth << endl;
	
	return 0;
}

六、小猫爬山

标签:搜索、深度优先搜索、DFS

思路:本质上跟上一道题是一模一样,就是 c h e c k check check 函数变了而已。另外这里用到了几个常用的剪枝,首先一般这种重量的都会从大到小去遍历,因为越大放的东西越少,分支就少了。其次就是最优性剪枝和可行性剪枝了,对于一些枚举组的,要用到排除等效冗余,其余的就是正常写法了。

题目描述:

翰翰和达达饲养了 N 只小猫,这天,小猫们要去爬山。

经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。

翰翰和达达只好花钱让它们坐索道下山。

索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是 C1、C2……CN。

当然,每辆缆车上的小猫的重量之和不能超过 W。

每租用一辆缆车,翰翰和达达就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?

输入格式
第 1 行:包含两个用空格隔开的整数,N 和 W。

第 2..N+1 行:每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci。

输出格式
输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。

数据范围
1≤N≤18,1≤Ci≤W≤108
输入样例:
5 1996
1
2
1994
12
29
输出样例:
2

示例代码1:朴素暴力

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 20, M = N, INF = 0x3f3f3f3f;

int n, m;
int w[N];
int sum[N];
int ans = N;

void dfs(int u, int k)
{
	if(k >= ans) return;  // 最优性剪枝 
	if(u == n)
	{
		ans = k;
		return;
	}
	
	for(int i = 0; i < k; ++i)
	{
		if((LL)w[u] + sum[i] <= m)  // 可行性剪枝
		{
			sum[i] += w[u];
			dfs(u+1,k);
			sum[i] -= w[u];
		}
	}
	
	sum[k] += w[u];
	dfs(u+1,k+1);
	sum[k] -= w[u];
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	cin >> n >> m;
	for(int i = 0; i < n; ++i) cin >> w[i];
	sort(w,w+n,greater<int>());  // 优化搜索顺序 
	
	dfs(0,0);
	
	cout << ans << endl;
	
	return 0;
}

示例代码2:迭代加深

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 20, M = N, INF = 0x3f3f3f3f;

int n, m;
int w[N];
int sum[N];

bool dfs(int u, int k, int depth)
{
	//这里要判断是否可行,而不是最优 
	if(k > depth) return false;  // 最优性剪枝
	if(u == n) return true; 
	
	for(int i = 0; i < k; ++i)
	{
		if((LL)w[u] + sum[i] <= m)  // 可行性剪枝
		{
			sum[i] += w[u];
			if(dfs(u+1,k,depth)) return true;
			sum[i] -= w[u];
		}
	}
	
	sum[k] += w[u];
	if(dfs(u+1,k+1,depth)) return true;
	sum[k] -= w[u];
	
	return false;
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	cin >> n >> m;
	for(int i = 0; i < n; ++i) cin >> w[i];
	sort(w,w+n,greater<int>());  // 优化搜索顺序 
	
	int depth = 0;
	while(!dfs(0,0,depth)) depth++;
	
	cout << depth << endl;
	
	return 0;
}

七、数独

标签:搜索、深度优先搜索、DFS

思路:这道题整体上是每次找到一个可以填入数字最少的一个(优化搜索顺序),然后填入继续迭代,直至所有的数都被填满了。这里运用了二进制的思想,把每一列看作一个二进制位,然后用二进制位的思想进行优化,具体细节见代码。

题目描述:

数独 是一种传统益智游戏,你需要把一个 9×9 的数独补充完整,使得数独中每行、每列、每个 3×3 的九宫格内数字 1∼9 均恰
好出现一次。

请编写一个程序填写数独。

输入格式
输入包含多组测试用例。

每个测试用例占一行,包含 81 个字符,代表数独的 81 个格内数据(顺序总体由上到下,同行由左到右)。

每个字符都是一个数字(1−9)或一个 .(表示尚未填充)。

您可以假设输入中的每个谜题都只有一个解决方案。

文件结尾处为包含单词 end 的单行,表示输入结束。

输出格式
每个测试用例,输出一行数据,代表填充完全后的数独。

输入样例:
4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end
输出样例:
417369825632158947958724316825437169791586432346912758289643571573291684164875293
416837529982465371735129468571298643293746185864351297647913852359682714128574936

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 9, M = 1 << N, INF = 0x3f3f3f3f;

int n, m;
int row[N], col[N], cell[3][3];
int ones[M], mmap[M];
string str;
int cnt;

void init()
{
	cnt = 0;
	for(int i = 0; i < N; ++i) row[i] = col[i] = (1 << N) - 1;
	for(int i = 0; i < 3; ++i)
	{
		for(int j = 0; j < 3; ++j)
		{
			cell[i][j] = (1 << N) - 1;
		}
	}
}

void draw(int x, int y, int t, bool is_set)
{
	if(is_set) str[x*N+y] = t + '1';
	else str[x*N+y] = '.';
	
	int v = 1 << t;
	if(!is_set) v = -v;
	
	row[x] -= v;
	col[y] -= v;
	cell[x/3][y/3] -= v;
}

int get(int x, int y)
{
	return row[x] & col[y] & cell[x/3][y/3];
}

int lowbit(int x)
{
	return x & -x;
}

bool dfs(int cnt)
{
	if(!cnt) return true;
	
	int minv = 10, x, y;
	for(int i = 0; i < N; ++i)
	{
		for(int j = 0; j < N; ++j)
		{
			if(str[i*N+j] == '.')
			{
				int state = get(i,j);
				if(ones[state] < minv)
				{
					minv = ones[state];
					x = i, y = j;
				}
			}
		}
	}
	
	int state = get(x,y);
	for(int i = state; i; i -= lowbit(i))
	{
		int t = mmap[lowbit(i)];
		draw(x,y,t,true);
		if(dfs(cnt-1)) return true;
		draw(x,y,t,false);
	}
	
	return false;
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	for(int i = 0; i < N; ++i) mmap[1 << i] = i;
	for(int i = 0; i < 1 << N; ++i)
	{
		for(int j = 0; j < N; ++j)
		{
			ones[i] += i >> j & 1;	
		}
	} 
	
	while(cin >> str, str != "end")
	{
		init();
		int cnt = 0;
		for(int i = 0; i < N; ++i)
		{
			for(int j = 0; j < N; ++j)
			{
				if(str[i*N+j] != '.')
				{
					int t = str[i*N+j] - '1';
					draw(i,j,t,true);
				}
				else cnt++;
			}
		}
		
		if(dfs(cnt)) cout << str << endl;
	}
	
	return 0;
}

八、木棒

标签:搜索、DFS、剪枝

思路:这道题是枚举组,因为只有当一组满了,才能去枚举下一组,如果当前组都不能填满,那就是错的。然后这个剪枝的策略也是比较的经典,可以看看。

题目描述:

乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 个长度单位。

然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。

请你设计一个程序,帮助乔治计算木棒的可能最小长度。

每一节木棍的长度都用大于零的整数表示。

输入格式
输入包含多组数据,每组数据包括两行。

第一行是一个不超过 64 的整数,表示砍断之后共有多少节木棍。

第二行是截断以后,所得到的各节木棍的长度。

在最后一组数据之后,是一个零。

输出格式
为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。

数据范围
数据保证每一节木棍的长度均不大于 50。

输入样例:
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
输出样例:
6
5

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 70, M = N, INF = 0x3f3f3f3f;

int n, m;
int w[N];
int len, sum;
bool st[N];

bool dfs(int u, int s, int start)
{
	if(u * len == sum) return true;  // 说明前u组已经满了 
	if(s == len) return dfs(u+1,0,0);
	
	for(int i = start; i < n; ++i)  // 排除性冗余 
	{
		if(!st[i] && (LL)w[i] + s <= len)
		{
			st[i] = true;
			if(dfs(u,s+w[i],i+1)) return true;
			st[i] = false;
			
			if(!s || s + w[i] == len) return false; 
			
			int j = i + 1;
			while(j < n && w[i] == w[j]) j++;
			i = j - 1;
		}
	}
	return false;
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	while(cin >> n, n)
	{
		memset(st, 0, sizeof st);
		len = sum = 0;
		for(int i = 0; i < n; ++i) 
		{
			cin >> w[i];
			sum += w[i];
			len = max(len, w[i]);
		}
		sort(w, w+n, greater<int>());  // 优化搜索顺序 

		while(true)
		{
			if(sum % len == 0 && dfs(0,0,0))
			{
				cout << len << endl;
				break;
			}
			len++;
		}
	}
	
	return 0;
}

九、加成序列

标签:搜索、迭代加深

思路:枚举每一个数,每一个数可能的情况,然后进行判断,最后的结果是否可行,然后利用迭代加深的性质找出最小值。一些搜索剪枝已经在代码中给出,更多细节见代码。

题目描述:

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

X[1]=1
X[m]=n
X[1]<X[2]<…<X[m−1]<X[m]
对于每个 k(2≤k≤m)都存在两个整数 i 和 j (1≤i,j≤k−1,i 和 j 可相等),使得 X[k]=X[i]+X[j]。
你的任务是:给定一个整数 n,找出符合上述条件的长度 m 最小的“加成序列”。

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

输入格式
输入包含多组测试用例。

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

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

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

每个输出占一行。

数据范围
1≤n≤100
输入样例:
5
7
12
15
77
0
输出样例:
1 2 4 5
1 2 4 6 7
1 2 4 8 12
1 2 4 5 10 15
1 2 4 8 9 17 34 68 77

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 110, M = N, INF = 0x3f3f3f3f;

int n, m;
int path[N];

bool dfs(int u, int depth)  // 正在选 下标为u的数 
{
	if(u > depth) return false;
	if(u == depth) return path[u-1] == n;
	
	bool st[N] = {0};
	for(int i = u - 1; i >= 0; --i)
	{
		for(int j = i; j >= 0; --j)  // 排除行冗余 
		{
			int s = path[i] + path[j];
			if(st[s] || s <= path[u-1] || s > n) continue;  // 排除性冗余
			
			st[s] = true;
			path[u] = s; 
			if(dfs(u+1,depth)) return true;
		}
	}
	
	return false;
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	while(cin >> n, n)
	{
		path[0] = 1;
		
		int depth = 1;
		while(!dfs(1,depth)) depth++;
		
		for(int i = 0; i < depth; ++i)
		{
			cout << path[i] << " ";
		}
		cout << endl;
	}
	
	return 0;
}

十、排书

标签:搜索、IDA*

思路:首先估价函数就是没排书一次会改变三个状态的正确性,然后最少需要 ⌈ r e s 3 ⌉ \lceil \frac{res}{3}\rceil 3res ,这个估价函数的设置必须是小于等于真实值的。然后进行迭代加深,根据其性质第一次可行即最优解,然后就是每次操作,进行暴力枚举所有可能的情况。

题目描述:

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

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

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

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

求最少需要多少次操作。

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

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

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

同行数之间用空格隔开。

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

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

每个结果占一行。

数据范围
1≤n≤15
输入样例:
3
6
1 3 4 6 2 5
5
5 4 3 2 1
10
6 8 5 3 4 7 2 9 1 10
输出样例:
2
3
5 or more

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 20, M = N, INF = 0x3f3f3f3f;

int n, m;
int w[N];

int f()
{
	int res = 0;
	for(int i = 1; i < n; ++i)
	{
		if(w[i] != w[i-1] + 1) res++;
	}
	return (res + 2) / 3;
}

bool dfs(int u, int depth)
{
	if(u + f() > depth) return false;  // 已经完成u次操作
	if(!f()) return true; 
	
	for(int len = 1; len <= n; ++len)
	{
		for(int l = 0; l + len - 1 < n; ++l)
		{
			int r = l + len - 1;
			for(int k = r + 1; k < n; ++k)  // 移动到k的后面
			{
				//把[r+1,k]移动到[l,r], 把[l,r]移动到之后
				int backup[N]; memcpy(backup, w, sizeof w);
				int i = l, j = r + 1;
				for(; j <= k; ++i, ++j) w[i] = backup[j];
				for(j = l; j <= r; ++i, ++j) w[i] = backup[j];
				if(dfs(u+1,depth)) return true;
				memcpy(w, backup, sizeof w);
			}
		}
	}
	return false;
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	int T; cin >> T;
	while(T--)
	{
		cin >> n;
		for(int i = 0; i < n; ++i) cin >> w[i];
		
		int depth = 0;
		while(depth < 5 && !dfs(0,depth)) depth++;
		
		if(depth >= 5) cout << "5 or more" << endl;
		else cout << depth << endl;
	}
	
	return 0;
}
  • 22
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lijiachang030718

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值