题目:
5-1:现有n种不同形状的宝石,每种n颗,共n*n颗。同一形状的n颗宝石分别具有n种不同的颜色c1,c2,…,cn中的一种颜色。欲将这n*n颗宝石排列成n行n列的一个方阵,使方阵中每一行和每一列的宝石都有n种不同的形状和n种不同颜色。是设计一个算法,计算出对于给定的n,有多少种不同的宝石排列方案。
思路:
约束条件:每种形状的宝石都具有n种不同的颜色,且每种颜色恰好出现一次。
根据约束条件判读是否符合,然后用回溯法暴力历遍。
程序代码:
#include <iostream>
#include <vector>
using namespace std;
bool isValid(vector<vector<int>>& grid, int row, int col, int n) {
// 检查行是否合法
for (int i = 0; i < n; i++) {
if (i != col && grid[row][i] == grid[row][col]) {
return false;
}
}
// 检查列是否合法
for (int i = 0; i < n; i++) {
if (i != row && grid[i][col] == grid[row][col]) {
return false;
}
}
// 检查同一形状的宝石是否具有不同的颜色
for (int i = 0; i < n; i++) {
if (i != row && grid[i][col] == grid[row][col]) {
return false;
}
if (i != col && grid[row][i] == grid[row][col]) {
return false;
}
}
return true;
}
void backtrack(vector<vector<int>>& grid, int row, int col, int n, int& count) {
if (row == n && col == 0) {
count++;
return;
}
for (int i = 1; i <= n; i++) {
bool colorExists = false;
for (int j = 0; j < n; j++) {
if (grid[row][j] == i || grid[j][col] == i) {
colorExists = true;
break;
}
}
if (colorExists) {
continue;
}
grid[row][col] = i;
int next_col = col + 1;
int next_row = row;
if (next_col == n) {
next_col = 0;
next_row++;
}
backtrack(grid, next_row, next_col, n, count);
grid[row][col] = 0;
}
}
// 计算给定形状和颜色数量时的不同宝石排列方案数
int countGemArrangements(int n) {
vector<vector<int>> grid(n, vector<int>(n, 0)); // 初始化宝石排列二维数组
int count = 0;
backtrack(grid, 0, 0, n, count); // 调用回溯函数
return count;
}
int main() {
int n = 2;
int result = countGemArrangements(n);
cout << "对于给定的n=" << n << ",共有" << result << "种不同的宝石排列方案。" << endl;
return 0;
}
结果:
题目:
5-2:罗密欧与朱丽叶身处一个m×n的迷宫中。每一个方格表示迷宫中的一个房间。这 m × n 个房间中有一些房间是封闭的,不允许任何人进入。在迷宫中任何位置均可沿 8 个方向进入未封闭的房间。罗密欧位于迷宫的(p,q)方格中,他必须找出一条通向朱丽叶所在的(r,s)方格的路。在抵达朱丽叶之前,他必须走遍所有未封闭的房间各一次,而且要使到达朱丽叶的转弯次数为最少。每改变一次前进方向算作转弯一次。请设计一个算法帮助罗密欧找出这样一条道路。
思路(参考ChatGPT):
创建一个m×n的二维数组maze来表示迷宫,其中0表示封闭的房间,1表示未封闭的房间。
创建一个m×n的二维数组visited来表示每个房间是否被访问过,初始值为false。
定义一个变量turns来记录转弯次数,初始值为0。
调用DFS函数,传入罗密欧的起始位置坐标和朱丽叶的位置坐标。
在DFS函数中,首先检查当前位置是否为朱丽叶的位置,如果是,则检查所有房间是否都被访问过,如果是,则返回true;否则返回false。
如果当前位置不是朱丽叶的位置,将当前位置标记为已访问,并遍历8个方向进行递归搜索。
在每个方向上,如果下一个位置是未封闭的房间且未被访问过,递归调用DFS函数,并根据情况更新转弯次数。
在递归调用结束后,将当前位置标记为未访问,恢复转弯次数,返回上一层。
最终返回DFS函数的结果。
程序代码:
#include <iostream>
#include <vector>
using namespace std;
const int MAX_M = 100;
const int MAX_N = 100;
int maze[MAX_M][MAX_N];
bool visited[MAX_M][MAX_N];
int m, n;
int minTurns = INT_MAX;
// 定义8个方向的偏移量
int dx[] = {-1, -1, 0, 1, 1, 1, 0, -1};
int dy[] = {0, 1, 1, 1, 0, -1, -1, -1};
bool isValid(int x, int y) {
return x >= 0 && x < m && y >= 0 && y < n;
}
bool isDestination(int x, int y, int r, int s) {
return x == r && y == s;
}
void DFS(int x, int y, int r, int s, int turns, int totalRooms) {
if (isDestination(x, y, r, s)) {
if (totalRooms + 1 == m * n) {
minTurns = min(minTurns, turns);
}
return;
}
visited[x][y] = true;
for (int i = 0; i < 8; ++i) {
int nextX = x + dx[i];
int nextY = y + dy[i];
if (isValid(nextX, nextY) && maze[nextX][nextY] && !visited[nextX][nextY]) {
int newTurns = (i % 2 == 0) ? turns : turns + 1;
DFS(nextX, nextY, r, s, newTurns, totalRooms + 1);
}
}
visited[x][y] = false;
}
int main() {
cout << "请输入迷宫的行数和列数:";
cin >> m >> n;
cout << "请输入迷宫的具体数据(1表示可通行,0表示不可通行):" << endl;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
cin >> maze[i][j];
}
}
int startX, startY, targetX, targetY;
cout << "请输入罗密欧的起始位置坐标(x, y):";
cin >> startX >> startY;
cout << "请输入朱丽叶的位置坐标(x, y):";
cin >> targetX >> targetY;
DFS(startX, startY, targetX, targetY, 0, 0);
if (minTurns == INT_MAX) {
cout << "未找到通向朱丽叶的路径" << endl;
} else {
cout << "找到通向朱丽叶的路径!最少转弯次数为: " << minTurns << endl;
}
return 0;
}
结果:
题目(参考ChatGPT):
5-3:网络设计问题:石油传输网络通常可表示为一个非循环带权的有向图G.G中有一个称为源的顶点s,石油从顶点输送到G中其他顶点,图G中每条边的权表示该边连接的2个顶点间的距离,网络中的油压随距离的增大而减小,为保证整个输油网络的正常工作,需要维持网络的最低油压Pmin,为此需要在网络的某处或全部顶点处设置增压器,在设置增压器的顶点处油压可以升至Pmax,油压从Pmax减到Pmin可使石油传输的距离至少为d,试设计一个算法,计算网络中增压器的最优设置方案,使得用最少增压器保证石油运输的畅通.
思路:
在生成最小生成树后,可以根据最小生成树的拓扑结构来确定增压器的设置。具体步骤如下:
- 找到最小生成树中的所有叶子节点(即只有一个邻接节点的节点),将它们作为候选的增压器位置。
- 对于每个候选增压器位置,计算从该位置到其他所有叶子节点的距离,并选择距离最长的一条路径。
- 如果选定的路径长度小于设定的最小传输距离
d
,则在该路径上设置增压器。 - 返回增压器的设置结果。
程序代码:
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;
const int MAX_V = 100; // 最大顶点数
struct Edge {
int v; // 目标顶点
int weight; // 边的权重
};
vector<Edge> graph[MAX_V]; // 图的邻接表表示
bool visited[MAX_V]; // 顶点是否已访问
int minDistance[MAX_V]; // 顶点到生成树的最短距离
int prim(int start, int Pmin, int Pmax, int d) {
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
fill_n(minDistance, MAX_V, INT_MAX); // 初始化最短距离为无穷大
minDistance[start] = 0; // 起始顶点到生成树的最短距离为0
pq.push(make_pair(0, start)); // 将起始顶点加入优先队列
int numBoosters = 0; // 增压器数量
while (!pq.empty()) {
int u = pq.top().second; // 当前最短距离对应的顶点
int dist = pq.top().first; // 当前最短距离
pq.pop();
if (visited[u]) {
continue;
}
visited[u] = true;
if (dist > d && dist <= Pmax) { // 如果当前最短距离超过d且小于等于Pmax,则需要设置增压器
numBoosters++;
minDistance[u] = 0; // 将当前顶点到生成树的最短距离设为0
}
for (const auto& edge : graph[u]) {
int v = edge.v;
int weight = edge.weight;
if (!visited[v] && weight < minDistance[v]) {
minDistance[v] = weight;
pq.push(make_pair(weight, v));
}
}
}
return numBoosters;
}
int main() {
int V = 5; // 顶点数
int E = 7; // 边数
// 预定义每条边的起点、终点和权重
vector<pair<pair<int, int>, int>> edges = {
{{0, 1}, 2},
{{0, 3}, 4},
{{1, 2}, 1},
{{1, 3}, 3},
{{1, 4}, 6},
{{2, 4}, 5},
{{3, 4}, 7}
};
cout << "请输入起始顶点的编号:";
int start = 0; // 起始顶点编号
cout << start << endl;
int Pmin = 3; // 最低油压 Pmin
int Pmax = 6; // 最高油压 Pmax
int d = 2; // 至少传输距离 d
for (int i = 0; i < E; ++i) {
int u = edges[i].first.first;
int v = edges[i].first.second;
int weight = edges[i].second;
graph[u].push_back({v, weight});
graph[v].push_back({u, weight});
}
int numBoosters = prim(start, Pmin, Pmax, d);
cout << "需要设置的最少增压器数量为:" << numBoosters << endl;
return 0;
}
结果: