路径之谜(蓝桥国赛真题-算法-回溯算法)

引题

在一个n*n的方阵中,找到一条左上角到右下角的路径,每个格子只能走一次,现在两个整数数组col,row,对应每行每列,要求路径所经过的格子按照行列分别计数,等于对应的值,其中0<=n<=20且答案唯一;

a8f64260e5d94905bb4650e50486a6bf.png

 

输入格式:

第一行输入格子的行列的长度n;

第二行输入行数组row[x];

第三行数入列数组col[y];

输出格式:

输出一行整数表示路径;

分析

路径的种类有很多,但答案路径只有一种,不能确定寻路次数,因此for循环是无法使用的,这里我们使用回溯算法,并且使用auto关键字来进行数据访问

分步实现:

第一步 确定状态

因为每个格子只能走一遍,所以我们开一个二维数组来记录格子的访问情况,又因为每个格子只能走一次,所以这里我们使用bool类型的数组

class solution

{public:

       vector<int>pathpuzzle(int col[], int row[], int n) {

              vector<vector<bool>>vis(n, vector<bool>(n, false));// 这里开一个数组来记录路径

              vector<int>path;// 同时也要确定格子所在的下标

              function<bool(int, int)>dfs = [&](int x, int y) //将基础类型作为参数,其他的作为全局上下文

{

                     };

       }

};

第二步 确定非法状态

某些状态是不符合题目条件的,遇到这种状态直接返回。当前坐标不在网格中;当前坐标已被走过;当前行列的格子数达到上限。

class solution

{public:

       vector<int>pathpuzzle(int col[], int row[], int n) {

              vector<vector<bool>>vis(n, vector<bool>(n, false));

              vector<int>path;

              function<bool(int, int)>dfs = [&](int x, int y) {

                     if (x < 0 || x >= n || y < 0 || y >= n) return false;// 当前坐标不在网格中

                     if (vis[x][y]) return false;// 当前坐标已被走过

                     if (row[x] == 0 || col[y] == 0) return false;// 当前行列的格子数达到上限

}

}

};

第三步 更新状态

当行列数目减一,就标记当前格子为已走过,添加路径数组为当前路径坐标

class solution

{public:

       vector<int>pathpuzzle(int col[], int row[], int n) {

              vector<vector<bool>>vis(n, vector<bool>(n, false));

              vector<int>path;

              function<bool(int, int)>dfs = [&](int x, int y) {

                     if (x < 0 || x >= n || y < 0 || y >= n) return false;

                     if (vis[x][y]) return false;

                     if (row[x] == 0 || col[y] == 0) return false;

                     row[x]--;

                     col[y]--;

                     vis[x][y] = true;// 当行列数目减一,就标记当前格子为已走过

                     path.push_back(x * n + y);// 添加路径数组为当前路径坐标

              }

}

};

第四步 结束状态

当坐标达到终点且行列数组的最大可用格子数为0

class solution

{public:

       vector<int>pathpuzzle(int col[], int row[], int n) {

              vector<vector<bool>>vis(n, vector<bool>(n, false));

              vector<int>path;

              function<bool(int, int)>dfs = [&](int x, int y) {

                     if (x < 0 || x >= n || y < 0 || y >= n) return false;

                     if (vis[x][y]) return false;

                     if (row[x] == 0 || col[y] == 0) return false;

                     row[x]--;

                     col[y]--;

                     vis[x][y] = true;

                     path.push_back(x * n + y);

                       if (x == n - 1 && y == n - 1 &&

                            accumulate(row, row + n, 0) == 0 &&

                            accumulate(col, col + n, 0) == 0) return true;// 当坐标达到终点且行列数组的最大可用格子数为0

                     }

}

};

第五步 状态转移

枚举下个状态,当前格子往上下左右四个方向进行运动,当找到一个合法路径时,直接返回

class solution

{

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

int dy[4] = { 1,-1,0,0 };//当前格子往上下左右四个方向进行运动

public:

       vector<int>pathpuzzle(int col[], int row[], int n) {

              vector<vector<bool>>vis(n, vector<bool>(n, false));

              vector<int>path;

              function<bool(int, int)>dfs = [&](int x, int y) {

                     if (x < 0 || x >= n || y < 0 || y >= n) return false;

                     if (vis[x][y]) return false;

                     if (row[x] == 0 || col[y] == 0) return false;

                     row[x]--;

                     col[y]--;

                     vis[x][y] = true;

                     path.push_back(x * n + y);

                        if (x == n - 1 && y == n - 1 &&

                            accumulate(row, row + n, 0) == 0 &&

                            accumulate(col, col + n, 0) == 0) return true;

                     }

}

};

第六步 状态还原

进行更新状态相反的操作,行列最大格子数目加一,将返回后的格子未标记,删除路径数组的路径

class solution

{

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

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

public:

       vector<int>pathpuzzle(int col[], int row[], int n) {

              vector<vector<bool>>vis(n, vector<bool>(n, false));

              vector<int>path;

              function<bool(int, int)>dfs = [&](int x, int y) {

                     if (x < 0 || x >= n || y < 0 || y >= n) return false;

                     if (vis[x][y]) return false;

                     if (row[x] == 0 || col[y] == 0) return false;

                     row[x]--;

                     col[y]--;

                     vis[x][y] = true;

                     path.push_back(x * n + y);

                     if (x == n - 1 && y == n - 1 &&

                            accumulate(row, row + n, 0) == 0 &&

                            accumulate(col, col + n, 0) == 0) return true;

                     for (int d = 0; d < 4; d++) {

                            if (dfs(x + dx[d], y + dy[d])) return true;

                     }

                     row[x]++;

                     col[y]++;

                     vis[x][y] = false;

                     path.pop_back();

                     return false;

                     };

              dfs(0, 0);

              return path;

       }

};

第七步 状态剪枝

回溯算法的精髓,能大大减少回溯的时间,避免做不必要的循环

我们发现:当某列格子最大数目为0,但其左侧的格子不为0时,路线将无法做到清零和达到右侧格子中,故我们将其加入判断中

class solution

{

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

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

public:

       vector<int>pathpuzzle(int col[], int row[], int n) {

              vector<vector<bool>>vis(n, vector<bool>(n, false));

              vector<int>path;

              function<bool(int, int)>dfs = [&](int x, int y) {

                     if (x < 0 || x >= n || y < 0 || y >= n) return false;

                     if (vis[x][y]) return false;

                     if (row[x] == 0 || col[y] == 0) return false;

                     if (row[x] == 1 && accumulate(row, row + n, 0) != 0) return false;

                     if (col[y] == 1 && accumulate(col, col + n, 0) != 0) return false;

                     row[x]--;

                     col[y]--;

                     vis[x][y] = true;

                     path.push_back(x * n + y);

                     if (x == n - 1 && y == n - 1 &&

                            accumulate(row, row + n, 0) == 0 &&

                            accumulate(col, col + n, 0) == 0) return true;

                     for (int d = 0; d < 4; d++) {

                            if (dfs(x + dx[d], y + dy[d])) return true;

                     }

                     row[x]++;

                     col[y]++;

                     vis[x][y] = false;

                     path.pop_back();

                     return false;

                     };

              dfs(0, 0);

              return path;

       }

};

对于不同的状态,回溯的第七步可能存在顺序和写法上的差异,根据实际情况进行修改。

最后上全代码:

using namespace std;
class solution
{
	int dx[4] = { 0,0,1,-1 };
	int dy[4] = { 1,-1,0,0 };
public:
	vector<int>pathpuzzle(int col[], int row[], int n) {
		vector<vector<bool>>vis(n, vector<bool>(n, false));
		vector<int>path;
		function<bool(int, int)>dfs = [&](int x, int y) {
			if (x < 0 || x >= n || y < 0 || y >= n) return false;
			if (vis[x][y]) return false;
			if (row[x] == 0 || col[y] == 0) return false;
			if (row[x] == 1 && accumulate(row, row + n, 0) != 0) return false;
			if (col[y] == 1 && accumulate(col, col + n, 0) != 0) return false;
			row[x]--;
			col[y]--;
			vis[x][y] = true;
			path.push_back(x * n + y);
			if (x == n - 1 && y == n - 1 &&
				accumulate(row, row + n, 0) == 0 &&
				accumulate(col, col + n, 0) == 0) return true;
			for (int d = 0; d < 4; d++) {
				if (dfs(x + dx[d], y + dy[d])) return true;
			}
			row[x]++;
			col[y]++;
			vis[x][y] = false;
			path.pop_back();
			return false;
			};
		dfs(0, 0);
		return path;
	}
};
int main() {
	int n;
	cin >> n;
	int row[10001], col[10001];
	for (int i = 0; i < n; i++)cin >> row[i];
	for (int i = 0; i < n; i++)cin >> col[i];
	auto path = (new solution())->pathpuzzle(row, col, n);
	for (int i = 0; i < path.size(); i++)cout << path[i] << " ";
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值