Fibonacci Paths
Fibonacci Paths
图论 + dp
题意简述
给定一个有向图,\(n\) 个顶点,\(m\) 条边。每个顶点 \(v\) 有一个正整数权值 \(a_v\)。
统计所有至少包含两个顶点的简单路径,使得路径上顶点权值依次组成的序列是广义斐波那契数列。
广义斐波那契数列定义:序列 \(x_0, x_1, \dots, x_k\) 满足
- \(x_0, x_1\) 为任意自然数;
- 对于 \(i \ge 2\),有 \(x_i = x_{i-1} + x_{i-2}\)。
答案对 \(998244353\) 取模。
关键观察
-
路径长度很短
因为所有 \(a_i \ge 1\),广义斐波那契数列从第三项开始严格递增且增长极快。
由于 \(a_i \le 10^{18}\),任何合法路径的长度最多约 \(90\)。 -
简单路径条件自动满足
广义斐波那契序列从第三项开始严格递增,因此后面的权值一定大于前面的权值,顶点不可能重复。
所以题目要求的“简单路径”无需额外判断,只需统计所有有向路径。 -
动态规划可行
设 \(dp[v \to u]\) 表示以有向边 \(v \to u\) 结尾的合法路径数量。
对于边 \(v \to u\),若存在前驱边 \(w \to v\) 满足\[a_w + a_v = a_u \]则所有以 \(w \to v\) 结尾的路径都可以接上 \(u\),形成新的合法路径。
DP 转移
对于每条边 \(v \to u\):
其中 \(1\) 表示路径 \(v \to u\) 本身(长度为 \(2\),一定合法)。
直接枚举所有 \(w\) 会超时,因此为每个顶点 \(v\) 维护一个哈希表 mp[v]:
- 键:起点权值 \(a_w\);
- 值:所有以 \(w \to v\) 结尾的路径数量之和,即 \(\sum dp[w \to v]\)。
处理边 \(v \to u\) 时,只需计算目标权值:
若 \(\text{target} > 0\),则从 mp[v] 中取出 target 对应的值累加即可。
处理顺序
由于 \(a_u = a_w + a_v > a_v\),所以边 \(v \to u\) 的终点权值 \(a_u\) 一定大于边 \(w \to v\) 的终点权值 \(a_v\)。
因此,将所有边按照终点的权值 \(a_u\) 从小到大排序,依次处理。
这样在处理边 \(v \to u\) 时,所有可能作为前驱的边 \(w \to v\) 都已经处理完毕,mp[v] 中已经存好了需要的数据。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
void solve() {
int n, m; cin >> n >> m;
vector<ll> weight(n + 1);
for(int i = 1; i <= n; i ++) cin >> weight[i];
vector<pair<int, int>> edge;
for(int i = 1; i <= m; i ++) {
int u, v; cin >> u >> v;
edge.push_back({u, v});
}
sort(edge.begin(), edge.end(), [&](auto& a, auto& b){
return weight[a.second] < weight[b.second];
});
ll ans = 0;
vector<unordered_map<ll, ll>> dp(n + 1);
for(auto [u, v] : edge) {
ll cur = 1;
ll t = weight[v] - weight[u];
if(t > 0) {
auto it = dp[u].find(t);
if(it != dp[u].end()) cur = (cur + it->second) % MOD;
}
dp[v][weight[u]] = (dp[v][weight[u]] + cur) % MOD;
ans = (ans + cur) % MOD;
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int _ = 1; cin >> _;
while(_--) solve();
return 0;
}

浙公网安备 33010602011771号