这是一个**带权图上的最短路径扩展问题**,但目标不是求最短时间,而是:
对于每个询问 $(x_i, l_i, r_i)$,要统计在时间区间 $[l_i, r_i]$ 内,有多少个整数时间点 $t$,使得从大厦 1 到大厦 $x_i$ 存在一条总耗时恰好为 $t$ 的路径。
注意:
- 高级廊桥耗时 1(对应 o=0)
- 低级廊桥耗时 2(对应 o=1)
所以每条边的权重是 1 或 2。
---
### 🔍 问题分析
我们的问题转化为:
> 对于每个节点 $x$,找出所有能以 **恰好 t 时间** 从节点 1 到达 $x$ 的时间 $t$,然后对每个查询 $[l_i, r_i]$ 统计满足条件的 $t$ 的数量。
这类似于“可达时间集合”的问题。但由于 $r_i \leq 10^9$,不可能枚举所有时间。
但是注意到:边权只有 1 和 2 —— 这提示我们可以使用 **分层图 + Dijkstra 扩展 + 同余类最短路(经典技巧)** 来解决。
---
## ✅ 解法:同余最短路(基于模最小公倍数的状态压缩)
由于边权是 1 和 2,它们的最小公倍数是 2。因此我们可以用 **模 2 分类讨论**。
### 🧠 核心思想:同余最短路(或称“dist 数组按 mod 分层”)
定义:
`dp[u][r] = 从节点 1 出发到达节点 u,且路径长度 ≡ r (mod 2) 的最小可能距离`
为什么可以这样做?
因为一旦我们知道某个点 u 可以在某个时间 d 到达,并且我们知道该点在模 2 意义下的最小可达时间,那么通过不断加 2(走一个权为 2 的环),就可以构造出所有大于等于这个最小值、且同余的更大时间。
更正式地:
如果存在一个环,其总权值为偶数(比如两个权为 1 的边组成环 → 总权为 2),那么只要我能以时间 $t$ 到达某点,我就能以 $t+2, t+4, ...$ 到达该点。
而本题中所有的边权都是 1 或 2,所以任意路径长度的变化步长最多是 1 或 2,但关键是:**只要有某个偶数长度的环可用,就可以无限增加偶数时间**。
但我们不需要显式找环,而是利用如下结论:
> 设 `dist[v][r]` 表示从起点到 v 的路径中,长度模 2 等于 r 的最小距离。
>
> 那么对于任意 $t \geq dist[v][r]$ 且 $t \equiv r \pmod{2}$,如果 $t$ 足够大,则一定存在一条从 1 到 v 的路径,其长度恰好为 $t$。
这是基于 **Frobenius Number / Coin Problem** 的推广:给定步长 1 和 2,能表示所有 ≥ 某个阈值的整数(实际上只要有 1 就能表示所有足够大的数)。
但因为我们图中有边权为 1 的边(高级桥),所以一旦某个点可以通过某种方式到达,且我们知道了它模 2 下的两个最小可达时间(如果有),那之后每隔 2 都可以到达。
---
### ✅ 具体步骤:
#### 步骤 1:建图
- 建立无向图,每条边有权重:o == 0 → w = 1;o == 1 → w = 2
#### 步骤 2:运行 Dijkstra 在状态空间 `(node, parity)` 上
- 状态:`(u, p)`,其中 p = 当前路径总时间 mod 2
- `dist[u][p]`:记录到达节点 u 且路径总时间 ≡ p (mod 2) 的最小时间
- 初始化:`dist[1][0] = 0`
- 使用优先队列进行松弛操作
#### 步骤 3:预处理每个节点 x 的“可达时间集合”在区间内的计数函数
- 对每个节点 x,我们有两个值:
- `d0[x] = dist[x][0]`:最小偶数时间到达 x
- `d1[x] = dist[x][1]`:最小奇数时间到达 x
- 那么对于任意时间 $t$,若:
- $t \geq d0[x]$ 且 $t \equiv 0 \pmod{2}$ → 可达
- $t \geq d1[x]$ 且 $t \equiv 1 \pmod{2}$ → 可达
> ⚠️ 注意:由于图中包含权为 1 的边,所以只要某个同余类有解,那么超过最小值的所有同余类时间都可达(因为你可以来回走一条权为 1 的边形成长度为 2 的环来递增时间)
#### 步骤 4:回答每个查询
对于每个查询 $(x, l, r)$:
我们要计算在 $[l, r]$ 中有多少个时间 $t$,满足:
- $t \geq d0[x]$ 且 $t \equiv 0 \pmod{2}$,或者
- $t \geq d1[x]$ 且 $t \equiv 1 \pmod{2}$
并且去重(一个 t 不会同时满足两种情况)
即:
```text
ans = count(t ∈ [l,r], t >= d0[x], t % 2 == 0)
+ count(t ∈ [l,r], t >= d1[x], t % 2 == 1)
```
如何快速计算?
定义函数:
`count_in_range(low, high, r)`:在 [low, high] 中有多少个整数 ≡ r (mod 2)
等价于:
- 如果 low > high: 0
- 否则:
- 第一个 ≥ low 且 ≡ r (mod 2) 的数:`start = low + ((r - low % 2 + 2) % 2)`
- 若 start > high: 0
- 否则: `((high - start) / 2) + 1`
也可以简化为:
```cpp
int count_parity(int L, int R, int rem) {
if (L > R) return 0;
int first = L + (rem - L % 2 + 2) % 2;
if (first > R) return 0;
return (R - first) / 2 + 1;
}
```
然后:
```cpp
ans = 0;
if (d0[x] <= R) {
ans += count_parity(max(L, d0[x]), R, 0);
}
if (d1[x] <= R) {
ans += count_parity(max(L, d1[x]), R, 1);
}
```
---
### 💡 边界与初始化注意事项
- 初始时 `dist[1][0] = 0`,`dist[1][1] = INF`
- 其他节点初始均为 INF
- 如果某个 `dist[x][r] == INF`,说明无法以该奇偶性到达,则忽略
- INF 应设为足够大(如 1e18),防止溢出
---
### ✅ C++ 实现代码
```cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 1e18;
const int MAXN = 100000 + 5;
const int MOD = 2;
struct State {
ll d;
int u, p;
bool operator<(const State& other) const { return d > other.d; }
};
vector<pair<int, int>> graph[MAXN]; // to, weight
ll dist[MAXN][MOD];
void dijkstra(int n) {
priority_queue<State> pq;
memset(dist, 0x3f, sizeof(dist));
dist[1][0] = 0;
pq.push({0, 1, 0});
while (!pq.empty()) {
auto [d, u, p] = pq.top(); pq.pop();
if (d != dist[u][p]) continue;
for (auto &[v, w] : graph[u]) {
int np = (p + w) % MOD;
ll nd = d + w;
if (nd < dist[v][np]) {
dist[v][np] = nd;
pq.push({nd, v, np});
}
}
}
}
int count_parity(ll L, ll R, int rem) {
if (L > R) return 0;
ll first = L + (rem - L % 2 + 2) % 2;
if (first > R) return 0;
return (R - first) / 2 + 1;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 0; i < m; ++i) {
int x, y, o;
cin >> x >> y >> o;
int w = (o == 0) ? 1 : 2;
graph[x].emplace_back(y, w);
graph[y].emplace_back(x, w);
}
dijkstra(n);
int q;
cin >> q;
while (q--) {
int x; ll l, r;
cin >> x >> l >> r;
ll total = 0;
if (dist[x][0] <= r) {
total += count_parity(max(l, dist[x][0]), r, 0);
}
if (dist[x][1] <= r) {
total += count_parity(max(l, dist[x][1]), r, 1);
}
cout << total << '\n';
}
return 0;
}
```
---
### 📌 复杂度分析
- 时间复杂度:
- Dijkstra:$O((n + m) \log n)$,每个节点最多 2 个状态
- 每个查询:$O(1)$
- 空间复杂度:$O(n + m)$
适用于 $n, m, q \leq 10^5$
---
### ❗ 特殊情况处理
- 图是连通的(题目保证小 Y 可以从任意大厦到任意大厦)
- 起点是 1
- 所有边权为 1 或 2 → 模 2 分层有效
- 因为有权为 1 的边,所以一旦某个奇偶类可达,后续每隔 2 都可达
---
### ✅ 测试样例验证
#### 输入 #1
```
3 3
1 2 1 -> w=2
2 3 1 -> w=2
1 3 0 -> w=1
1
3 3 4
```
路径:
- 1→3 直接:时间 1(奇)
- 1→2→3:时间 4(偶)
所以到达 3 的时间集合:
- 奇:1, 3, 5, ...
- 偶:4, 6, 8, ...
但在 [3,4] 区间内:
- 时间 3:奇,≥1 → 可达
- 时间 4:偶,≥4 → 可达
所以答案是 2 ✅
输出:2
#### 输入 #2
略,逻辑类似,程序应正确输出 8,7,8,8
---