题目解释
题目要求解决 图的m着色问题。给定一个无向图 和 种不同的颜色,你需要为图的每个顶点分配一种颜色,且满足以下条件:
• 每条边的两个端点必须着不同的颜色。
目标:找出所有合法的着色方案,并输出其数量。
输入格式:
1. 第一行给出三个整数:(顶点数)、(边数)、(可用颜色数)。
2. 接下来 行,每行包含两个整数 和 ,表示图中一条边()。这是一个无向图,因此边 和 被视为相同。
输出格式:
• 输出一个整数,表示所有不同的合法着色方案的数量。
示例解析:
输入:
5 8 4
1 2
1 3
1 4
2 3
2 4
2 5
3 4
4 5
这个图有 5 个顶点(编号从 1 到 5)和 8 条边。我们有 4 种颜色可以为每个顶点着色。
图结构:
1
/|\
2 3 4
\|/
5
输出:
48
意味着有 48 种不同的着色方案可以满足题目要求,即每条边的两个顶点的颜色不同。
代码解释
接下来我们一步步解释代码的实现。
1. 定义数据结构
int n, e, m; // n: 顶点数,e: 边数,m: 可用颜色数
vector<vector<int>> adj; // 图的邻接表
vector<int> colors; // 记录每个顶点的颜色
int result = 0; // 记录有效的着色方案数
• n: 顶点数。
• e: 边数。
• m: 可用的颜色数。
• adj: 邻接表,表示图中每个顶点的邻接顶点。
• colors: 记录每个顶点的颜色,初始时顶点没有被着色,默认值是 0。
• result: 记录合法的着色方案总数。
2. 读取输入数据并构建图
cin >> n >> e >> m; // 读取顶点数、边数和颜色数
adj.resize(n); // 初始化邻接表
colors.resize(n, 0); // 初始化颜色数组,0表示未着色
for (int i = 0; i < e; i++) {
int u, v;
cin >> u >> v;
adj[u - 1].push_back(v - 1); // 无向图,所以要同时添加两条边
adj[v - 1].push_back(u - 1);
}
• 通过 cin 读取顶点数 、边数 和颜色数 。
• 初始化邻接表 adj,其大小为 ,每个顶点的邻接顶点列表开始为空。
• 使用 resize 初始化颜色数组 colors,初始值为 0,表示所有顶点均未着色。
• 使用 for 循环输入图中的边,将边的信息填充到邻接表中。需要注意的是,输入的顶点编号是从 1 开始的,但是数组的索引从 0 开始,因此我们将顶点编号减 1。
3. 回溯算法
3.1 合法性检查函数 isValid
bool isValid(int vertex, int color) {
for (int neighbor : adj[vertex]) {
if (colors[neighbor] == color) {
return false;
}
}
return true;
}
• isValid 函数用于检查当前顶点 vertex 是否可以涂上颜色 color。
• 我们检查顶点 vertex 的所有邻接顶点(在 adj[vertex] 中),如果某个邻接顶点已经着了相同的颜色,则当前选择的颜色 color 就不合法,返回 false。
• 如果所有邻接顶点都没有着相同的颜色,则返回 true,表示颜色合法。
3.2 回溯函数 backtrack
void backtrack(int vertex) {
if (vertex == n) { // 如果所有顶点都已着色
result++; // 找到一种有效的着色方案
return;
}
// 尝试为当前顶点vertex选择不同的颜色
for (int color = 1; color <= m; color++) {
if (isValid(vertex, color)) { // 如果当前颜色合法
colors[vertex] = color; // 为当前顶点着色
backtrack(vertex + 1); // 递归处理下一个顶点
colors[vertex] = 0; // 回溯,撤销选择
}
}
}
• backtrack 函数用于尝试为每个顶点选择合法的颜色,并递归地为下一个顶点选择颜色。
• 终止条件:当 vertex == n 时,表示所有顶点都已经着色,且这是一个合法的着色方案,因此我们将 result 增加 1。
• 颜色选择:对于每个顶点,尝试所有 1 到 的颜色。如果某个颜色合法(通过 isValid 检查),则为当前顶点着色,并递归地尝试下一个顶点。
• 回溯:递归调用结束后,恢复当前顶点的颜色为 0,即撤销颜色的选择,以尝试其他可能的选择。
4. 开始回溯并输出结果
backtrack(0);
cout << result << endl; // 输出结果
• 从第一个顶点(顶点 0)开始回溯,尝试为所有顶点选择颜色。
• 当回溯完成后,输出所有合法的着色方案数。
复杂度分析:
1. 时间复杂度:
• 每个顶点有 种可能的颜色选择,因此在最坏情况下,回溯算法的时间复杂度是 ,其中 是顶点数, 是颜色数。
• 因为每个顶点都可以有 种选择,所以总的回溯搜索树的规模是 。
2. 空间复杂度:
• 主要是图的邻接表和颜色数组的存储空间,空间复杂度是 ,其中 是顶点数, 是边数。
• 邻接表需要存储 条边,颜色数组需要存储 个顶点的颜色。
总结:
这道题目要求我们使用回溯算法来解决图的着色问题。回溯算法的核心思想是尝试所有可能的着色方式,并检查每种方式是否合法。通过递归和回溯,我们可以遍历所有顶点的颜色选择,从而计算出所有合法的着色方案。
完整代码
#include <iostream>
#include <vector>
using namespace std;
int result = 0; // 存储合法的着色方案数
vector<int> path; // 存储每个顶点的颜色
// 判断当前的着色是否合法
bool isValid(const vector<vector<int> >& graph, int x, int color) {
for (size_t i = 0; i < graph[x].size(); ++i) {
int neighbor = graph[x][i];
if (path[neighbor] == color) {
return false; // 如果相邻的顶点有相同的颜色,返回false
}
}
return true;
}
// 回溯函数
void backtrack(int n, const vector<vector<int> >& graph, int x, int m) {
if (x == n) { // 如果所有顶点都已经着色
result++; // 找到一种合法的着色方案
return;
}
// 尝试为顶点x选择不同的颜色
for (int i = 1; i <= m; i++) { // 遍历颜色1到m
if (isValid(graph, x, i)) { // 如果当前着色合法
path[x] = i; // 为顶点x着色
backtrack(n, graph, x + 1, m); // 递归处理下一个顶点
path[x] = 0; // 回溯,撤销选择
}
}
}
int main() {
int n, e, m;
cin >> n >> e >> m; // 输入顶点数、边数和颜色数
vector<vector<int> > graph(n); // 初始化邻接表
// 读取图的边
for (int i = 0; i < e; i++) {
int u, v;
cin >> u >> v;
graph[u - 1].push_back(v - 1); // 无向图,所以添加两条边
graph[v - 1].push_back(u - 1);
}
path.resize(n, 0); // 初始化path数组,0表示未着色
backtrack(n, graph, 0, m); // 从第0个顶点开始回溯
cout << result << endl; // 输出合法的着色方案数
return 0;
}