Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
Every directed edge is already a valid path: with only two numbers, there is no Fibonacci condition to check yet. So start by counting edges, then figure out how longer paths extend.
Look at the last two vertices of a path, say . To append a next vertex , the value is forced: . But it is even cleaner to compute an edge from its possible previous vertex.
Let be the number of valid paths ending with the directed edge . If the path has length at least , the previous vertex must satisfy and .
Process edges by increasing value of their destination vertex. A transition into comes from some , and it only exists when , so . That means all needed previous states are already done.
For each vertex , maintain sums of already-computed grouped by . Then each edge is just one lookup for , plus one update under key at vertex .
Every directed edge is automatically valid. That is the freebie: a generalized Fibonacci sequence of length has arbitrary first two numbers, so there is no recurrence condition yet.
The real job is counting longer paths without doing some horrifying exponential path search. Spoiler: the graph can have cycles, but the values save us.
Edge DP
A valid path only needs its last two values to know what can come next. So use directed edges as DP states.
For an edge , define
The path can be just the two vertices , so every edge contributes at least :
Now suppose a longer valid path ends with
Then the Fibonacci rule forces
So, for a fixed edge , the previous vertex must satisfy
and there must be an edge .
Therefore:
If , then is not positive, so there is no previous natural value. In that case .
That recurrence is the whole problem. Everything else is just making it fast.
Why the order works
Any non-base transition into uses some previous edge with
Since all values are positive,
So the previous DP state has destination value , while the current one has destination value , and .
That means we can sort all directed edges by the value of their destination vertex and process them in that order. When we compute , every possible has already been computed.
No cycle drama. The values are doing the topological sort for us.
Fast grouped sums
The recurrence asks for:
for one specific value .
So for each vertex , maintain a table:
using only edges already processed.
When processing edge :
To avoid hash-map shenanigans, we can coordinate-compress separately per vertex. For every incoming edge , the only key ever needed in from that edge is . Store all such values for each vertex, sort and unique them, and use binary search.
About the “simple path” condition
This part looks scary, but it is basically bait.
For a valid sequence
we have
and after that the sequence strictly increases. So values cannot repeat, except possibly at the first two positions.
The first two vertices still cannot be the same vertex because the input has no self-loops. After that, every new value is larger than the previous relevant values, so no vertex can reappear. Thus every Fibonacci-valued directed walk counted here is already simple. Nice for us; mildly rude of the statement to make it sound worse.
Correctness proof
We prove that the algorithm counts exactly the valid paths.
First, every edge is initialized with one path, namely . This is valid because any two positive numbers form a generalized Fibonacci sequence of length .
Now consider a DP contribution added to from some previous edge . The algorithm only adds it when , which is equivalent to . Therefore appending to any valid path ending in preserves the Fibonacci recurrence. Since is an actual directed edge, the result is a valid directed path.
Conversely, take any valid path of length at least ending in . Its prefix ending in is also valid, and the recurrence gives . When the algorithm processes , the value is included in , so this path is counted.
The processing order is valid because every transition goes from destination value to destination value , and . Sorting edges by destination value guarantees all required previous states are ready.
Finally, the counted walks are simple because positive Fibonacci growth prevents repeated values after the first two positions, and self-loops do not exist. So the algorithm counts exactly the required simple paths.
Complexity
For each test case, we sort the edges and sort the per-vertex key lists. Each edge does a constant number of binary searches in its vertex-local list.
Total complexity:
Memory:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const ll MOD = 998244353;
struct Edge {
int from, to;
};
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
vector<ll> a(n);
for (ll &x : a) cin >> x;
vector<Edge> edges(m);
vector<vector<ll>> keys(n);
for (int i = 0; i < m; i++) {
int v, u;
cin >> v >> u;
--v;
--u;
edges[i] = {v, u};
keys[u].push_back(a[v]);
}
for (int v = 0; v < n; v++) {
sort(keys[v].begin(), keys[v].end());
keys[v].erase(unique(keys[v].begin(), keys[v].end()), keys[v].end());
}
vector<vector<ll>> sum(n);
for (int v = 0; v < n; v++) {
sum[v].assign(keys[v].size(), 0);
}
vector<int> order(m);
iota(order.begin(), order.end(), 0);
sort(order.begin(), order.end(), [&](int x, int y) {
ll ax = a[edges[x].to];
ll ay = a[edges[y].to];
if (ax != ay) return ax < ay;
return x < y;
});
ll ans = 0;
for (int id : order) {
int u = edges[id].from;
int v = edges[id].to;
ll ways = 1;
if (a[v] > a[u]) {
ll need = a[v] - a[u];
auto it = lower_bound(keys[u].begin(), keys[u].end(), need);
if (it != keys[u].end() && *it == need) {
int pos = int(it - keys[u].begin());
ways += sum[u][pos];
if (ways >= MOD) ways -= MOD;
}
}
ans += ways;
ans %= MOD;
auto it = lower_bound(keys[v].begin(), keys[v].end(), a[u]);
int pos = int(it - keys[v].begin());
sum[v][pos] += ways;
sum[v][pos] %= MOD;
}
cout << ans << '\n';
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const ll MOD = 998244353;
struct Edge {
int from, to;
};
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
vector<ll> a(n);
for (ll &x : a) cin >> x;
vector<Edge> edges(m);
vector<vector<ll>> keys(n);
for (int i = 0; i < m; i++) {
int v, u;
cin >> v >> u;
--v;
--u;
edges[i] = {v, u};
keys[u].push_back(a[v]);
}
for (int v = 0; v < n; v++) {
sort(keys[v].begin(), keys[v].end());
keys[v].erase(unique(keys[v].begin(), keys[v].end()), keys[v].end());
}
vector<vector<ll>> sum(n);
for (int v = 0; v < n; v++) {
sum[v].assign(keys[v].size(), 0);
}
vector<int> order(m);
iota(order.begin(), order.end(), 0);
sort(order.begin(), order.end(), [&](int x, int y) {
ll ax = a[edges[x].to];
ll ay = a[edges[y].to];
if (ax != ay) return ax < ay;
return x < y;
});
ll ans = 0;
for (int id : order) {
int u = edges[id].from;
int v = edges[id].to;
ll ways = 1;
if (a[v] > a[u]) {
ll need = a[v] - a[u];
auto it = lower_bound(keys[u].begin(), keys[u].end(), need);
if (it != keys[u].end() && *it == need) {
int pos = int(it - keys[u].begin());
ways += sum[u][pos];
if (ways >= MOD) ways -= MOD;
}
}
ans += ways;
ans %= MOD;
auto it = lower_bound(keys[v].begin(), keys[v].end(), a[u]);
int pos = int(it - keys[v].begin());
sum[v][pos] += ways;
sum[v][pos] %= MOD;
}
cout << ans << '\n';
}
}