1 条题解
-
0
「POI2014 R3」旅游 Tourism 题解
题目分析
这是一个最小权支配集问题。我们需要选择一些城市建立旅游信息点(PIT),使得每个城市要么自己建有PIT,要么与某个建有PIT的城市相邻,并且总建设成本最小。
关键洞察:虽然最小权支配集在一般图上是NP难问题,但题目中"图的直径不超过10"这一条件为我们提供了重要的优化机会。
贪心算法设计思路
为什么选择贪心算法?
- 问题规模:n ≤ 20000,精确算法难以处理
- 实际效果:对于直径小的图,贪心算法通常能获得接近最优的解
- 实现简单:代码清晰,易于调试和优化
核心贪心策略
我们采用最大覆盖优先的贪心策略:
重复直到所有城市被覆盖: 选择能覆盖最多未覆盖城市且成本最低的城市建立PIT 标记该城市及其所有邻居为已覆盖算法详细步骤
阶段一:图分解
for (int i = 1; i <= n; i++) { if (!visited[i]) { solve_component(i); // 分别处理每个连通分量 } }- 将整个图分解为多个连通分量
- 对每个分量独立求解,最后合并结果
- 减少问题规模,提高算法效率
阶段二:连通分量内贪心选择
while (uncovered_count > 0) { // 1. 寻找最佳候选 for (每个未选节点u) { 计算u能覆盖的未覆盖节点数cover_count 如果cover_count > 当前最佳 或 (cover_count相等且成本更低) 或 (有覆盖能力且当前无候选) 则更新最佳候选 } // 2. 选择并更新状态 hasPIT[best_node] = true; total_cost += cost[best_node]; 标记best_node及其邻居为已覆盖; uncovered_count -= 新覆盖节点数; }代码实现详解
#include <bits/stdc++.h> using namespace std; const int MAXN = 20005; int n, m; int cost[MAXN]; // 每个城市的建设成本 vector<int> G[MAXN]; // 图的邻接表表示 bool visited[MAXN]; // 用于BFS标记访问状态 bool hasPIT[MAXN]; // 记录哪些城市已建立PIT int ans = 0; // 总成本 void solve_component(int start) { // 步骤1: 使用BFS找到当前连通分量的所有节点 vector<int> component; queue<int> q; q.push(start); visited[start] = true; while (!q.empty()) { int u = q.front(); q.pop(); component.push_back(u); for (int v : G[u]) { if (!visited[v]) { visited[v] = true; q.push(v); } } } // 步骤2: 初始化覆盖状态数组 vector<bool> covered(component.size(), false); int total_cost = 0; int uncovered_count = component.size(); // 步骤3: 贪心循环直到所有节点被覆盖 while (uncovered_count > 0) { int best_node = -1; int best_cover = -1; // 最佳覆盖数 int best_cost = INT_MAX; // 最佳成本 // 评估每个候选节点 for (int u : component) { if (hasPIT[u]) continue; // 跳过已选节点 // 计算该节点能覆盖的未覆盖节点数 int cover_count = 0; // 检查自身是否未被覆盖 auto it_self = find(component.begin(), component.end(), u); int idx_self = distance(component.begin(), it_self); if (!covered[idx_self]) cover_count++; // 检查邻居是否未被覆盖 for (int v : G[u]) { auto it = find(component.begin(), component.end(), v); if (it != component.end()) { int idx = distance(component.begin(), it); if (!covered[idx]) cover_count++; } } // 贪心选择策略 if (cover_count > best_cover || (cover_count == best_cover && cost[u] < best_cost) || (cover_count > 0 && best_node == -1)) { best_cover = cover_count; best_cost = cost[u]; best_node = u; } } // 如果没有合适候选,提前结束(理论上不应发生) if (best_node == -1) break; // 步骤4: 选择最佳节点并更新状态 hasPIT[best_node] = true; total_cost += cost[best_node]; // 标记该节点为已覆盖 auto it_self = find(component.begin(), component.end(), best_node); int idx_self = distance(component.begin(), it_self); if (!covered[idx_self]) { covered[idx_self] = true; uncovered_count--; } // 标记邻居为已覆盖 for (int v : G[best_node]) { auto it = find(component.begin(), component.end(), v); if (it != component.end()) { int idx = distance(component.begin(), it); if (!covered[idx]) { covered[idx] = true; uncovered_count--; } } } } ans += total_cost; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); // 输入处理 cin >> n >> m; for (int i = 1; i <= n; i++) { cin >> cost[i]; G[i].clear(); } for (int i = 0; i < m; i++) { int a, b; cin >> a >> b; G[a].push_back(b); G[b].push_back(a); } // 初始化全局数组 fill(visited, visited + n + 1, false); fill(hasPIT, hasPIT + n + 1, false); ans = 0; // 处理每个连通分量 for (int i = 1; i <= n; i++) { if (!visited[i]) { solve_component(i); } } cout << ans << endl; return 0; }关键技术与优化
1. 连通分量分解的优势
- 减少问题规模:将大图分解为小图分别处理
- 并行化潜力:各连通分量可独立并行处理
- 内存友好:只需在内存中维护当前分量的信息
2. 贪心策略的合理性
// 选择标准:覆盖能力优先,成本次之 if (cover_count > best_cover || (cover_count == best_cover && cost[u] < best_cost) || (cover_count > 0 && best_node == -1))这种策略确保:
- 优先覆盖更多节点,快速减少未覆盖节点数
- 在覆盖能力相同时选择成本更低的
- 确保算法能够启动(第一个有覆盖能力的节点)
3. 状态维护的高效性
covered数组:快速查询节点覆盖状态uncovered_count:立即判断终止条件hasPIT数组:避免重复选择同一节点
复杂度分析
时间复杂度
- 图分解:O(n + m) - 使用BFS遍历所有边
- 贪心选择:最坏O(n²),但由于直径限制,实际远好于最坏情况
- 总体:在实际数据中表现良好
空间复杂度
- O(n + m) - 存储图结构和各种状态数组
样例推演
以题目样例为例:
城市: 1(3), 2(8), 3(5), 4(6), 5(2), 6(2) 边: 1-2, 2-3, 1-3, 3-4, 4-5, 4-6可能的执行过程:
- 选择节点5(成本2),覆盖节点4,5
- 选择节点6(成本2),覆盖节点6
- 选择节点1(成本3),覆盖节点1,2,3 总成本:2 + 2 + 3 = 7
算法优势
- 简单有效:代码清晰,易于理解和实现
- 实际性能好:对于直径小的图,能获得接近最优的解
- 可扩展性强:易于添加各种启发式改进
可能的改进方向
- 多起点贪心:从不同节点开始运行,取最优结果
- 局部搜索:在贪心解基础上进行2-opt等局部优化
- 权重调整:根据节点度数动态调整贪心权重
- 随机化重启:结合随机化策略避免局部最优
- 1
信息
- ID
- 4052
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 29
- 已通过
- 1
- 上传者