1 条题解
-
0
题解:葡萄藤上的百合花
题目大意
- 有 个节点(编号 )
- 两种移动方式:
- 葡萄藤边: 条双向边
- 闪现:任意两点 之间可花费 时间移动,其中
- 求从起点 到所有节点的最短时间
数据范围:
问题分析
直接建图不可行,因为闪现边数量为 ,会爆炸。
关键观察:闪现边的权值只依赖于两个节点的异或值(汉明距离)。
核心思路
设 表示从 到 的最短时间。
1. 葡萄藤边处理
直接作为普通边在 Dijkstra 中更新。
2. 闪现边处理
对于任意点 ,闪现到 的花费是 。
我们定义:
那么闪现更新可以表示为:
但枚举所有 不可行。
高效算法:分层更新
我们可以定期执行一次 全局闪现更新:
这等价于对 和 进行 最小值卷积:
$$d' = d \ast w \quad \text{其中} \quad (f \ast g)[x] = \min_y \{ f[y] + g[x \oplus y] \} $$这种卷积可以用 快速沃尔什变换(FWT) 的最小值版本在 时间内完成。
算法步骤
-
初始化
- ,其他
- (注意 )
-
Dijkstra 主循环
- 从优先队列取出 最小的
- 用葡萄藤边更新邻居
- 如果当前轮次需要全局更新,执行一次最小值卷积更新
-
输出
复杂度分析
- 葡萄藤边更新:
- 全局闪现更新:
- 总复杂度:,可以接受
代码实现要点
// 伪代码 void solve() { vector<long long> d(n, INF); vector<long long> w(n); // 初始化 w for (int mask = 0; mask < n; mask++) { w[mask] = a[__builtin_popcount(mask)]; } w[0] = 0; // 自己到自己的花费为0 d[s] = 0; priority_queue<pair<long long, int>> pq; pq.push({0, s}); while (!pq.empty()) { auto [dist, u] = pq.top(); pq.pop(); if (dist != d[u]) continue; // 葡萄藤边更新 for (auto [v, cost] : graph[u]) { if (d[v] > d[u] + cost) { d[v] = d[u] + cost; pq.push({d[v], v}); } } // 全局闪现更新(可以每几次迭代执行一次) vector<long long> new_d = d; for (int i = 0; i < k; i++) { for (int mask = 0; mask < n; mask++) { if (mask >> i & 1) { new_d[mask] = min(new_d[mask], new_d[mask ^ (1 << i)] + w[1 << i]); } } } // 需要多次迭代确保完全更新 for (int i = 0; i < k; i++) { for (int mask = 0; mask < n; mask++) { if (mask >> i & 1) { new_d[mask] = min(new_d[mask], new_d[mask ^ (1 << i)] + w[1 << i]); } } } // 更新 d 和优先队列 for (int i = 0; i < n; i++) { if (new_d[i] < d[i]) { d[i] = new_d[i]; pq.push({d[i], i}); } } } // 输出答案 for (int i = 0; i < n; i++) { cout << d[i] << " "; } }
样例解释
输入:
3 6 2 17 14 11 0 2 3 4 2 9 2 2 1 2 2 6 7 0 5 4 2 9
输出:
3 14 0 17 9 11 17 8
- (起点)
- (通过边 0-2)
- (通过边 4-2)
- 其他点通过闪现或组合路径到达
总结
本题的关键在于:
- 认识到闪现边权值只依赖于汉明距离
- 使用最小值卷积批量处理闪现更新
- 结合 Dijkstra 处理稀疏的葡萄藤边
这种 图论 + 状态压缩 + 卷积优化 的思路在处理大规模隐式图时非常有用。
- 1
信息
- ID
- 3296
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 7
- 已通过
- 1
- 上传者