跳转至

D. Fibonacci Paths

时间限制:每测试点2秒 内存限制:每测试点256 MB

问题描述

给定一个有向图,包含 n 个顶点和 m 条边。每个顶点 v 对应一个正整数 a_v。计算不同的简单路径^{*}的数量,这些路径至少包含两个顶点,并且路径上顶点的数字构成的序列形成一个广义斐波那契数列。

在这个问题中,我们认为数字序列 x_0, x_1, \dots, x_k 构成广义斐波那契数列,如果满足:

  • x_0, x_1 是任意自然数。
  • x_i = x_{i-2} + x_{i-1} 对所有 2 \le i \le k 成立。

注意,一个广义斐波那契数列至少包含两个数字。

由于答案可能很大,请输出其对 998244353 取模的结果。

^{*} 在有向图中,一条简单路径是顶点序列 v_1, v_2, \dots, v_k,满足图中的每个顶点在路径中最多出现一次,并且对于所有 i < k,存在一条从 v_iv_{i+1} 的有向边。

输入格式

每个测试包含多个测试用例。第一行包含测试用例的数量 t1 \le t \le 10^4)。接下来是每个测试用例的描述。

每个测试用例的第一行包含两个数字 n, m2 \le n \le 2 \cdot 10^5, 1 \le m \le 2 \cdot 10^5)—— 分别是图中顶点的数量和边的数量。

每个测试用例的第二行包含 n 个自然数 a_1, a_2, \dots, a_n1 \le a_i \le 10^{18})—— 写在顶点上的数字。

接下来的 m 行包含图的边;每条边由两个自然数 v, u 定义(1 \le v, u \le n, u \ne v),表示一条从 v 指向 u 的有向边。保证图中没有重边。

保证所有测试用例的 n 之和以及 m 之和均不超过 2 \cdot 10^5

输出格式

对于每个测试用例,输出形成广义斐波那契数列的路径数量,结果对 998244353 取模。

样例输入

4
4 4
3 4 3 6
1 2
1 3
2 4
3 4
4 6
1 1 1 2
1 2
2 3
3 1
1 4
2 4
3 4
8 11
2 4 2 6 8 10 18 26
1 2
2 3
3 1
4 3
2 4
3 5
5 6
4 6
6 7
7 5
5 8
2 2
10 10
1 2
2 1

样例输出

5
9
24
2

样例说明

第一个测试用例示例的解释(括号外是顶点编号,括号内是写在顶点的数字):

1(3) --> 2(4)
  |         |
  v         v
3(3) --> 4(6)

在这种情况下,有 5 条广义斐波那契路径:(1,2), (1,3), (2,4), (3,4), (1,3,4)。例如,对于路径 (1,3,4),该路径上顶点数字构成的序列是:[3,3,6]。可以很容易地看出,序列中的第三个数是前两个数的和。

第二个测试用例示例的解释:

1(1) --> 2(1)
  ^ \     | \
  |  \    v  v
  |   --> 4(2)
  |       ^
3(1) <---/

在这种情况下,有 9 条广义斐波那契路径:(1,2), (1,4), (2,3), (2,4), (3,1), (3,4), (1,2,4), (2,3,4), (3,1,4)。注意,对于路径 (1,2,3),该路径上顶点数字构成的序列是:[1,1,1],它不是一个广义斐波那契数列。

AI TLE 代码

// Problem: CF 2176 D
// Contest: Codeforces - Codeforces Round 1070 (Div. 2)
// URL: https://codeforces.com/contest/2176/problem/D
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <iostream>
#include <vector>
#include <map>
using namespace std;
const int MOD = 998244353;
const int MAXN = 200005; // 根据题目 N, M <= 2e5

struct Edge {
    int u;  // 起点
    int v;  // 终点
    int id; // 边的唯一编号 (0 到 m-1)
};

long long a[MAXN];       // 存储每个点的数值 (注意:题目数值高达 10^18,必须用 long long)
vector<Edge> all_edges;  // 存储所有的边,方便通过 ID 访问
// 格式:lookup[当前点u][来源点的数值] = {边ID列表}
map<long long, vector<int>> lookup[MAXN]; 
long long memo[MAXN]; 

long long dfs(int edge_idx) {
    // 步骤 1: 记忆化检查
    // TODO: 如果 memo[edge_idx] 已经被计算过(不等于 -1),直接返回它
    if(memo[edge_idx]!=-1) return memo[edge_idx];
    // 步骤 2: 获取当前边的信息
    // 通过 all_edges[edge_idx] 拿到 u (起点) 和 v (终点)
    auto [u,v,id]=all_edges[edge_idx];
    // 步骤 3: 初始化当前答案
    // 这条边本身就是一条合法的长度为 2 的路径,所以初始值为 1
    long long ans = 1;

    // 步骤 4: 逆向推导
    // 当前是 u -> v。
    // 如果存在上一条边 p -> u,那么必须满足:a[p] + a[u] == a[v]
    // 所以我们需要找的“目标前任值” target = a[v] - a[u]
    long long target = a[v]-a[u];

    // 步骤 5: 查找并递归
    // 只有当 target >= 1 时才查找(因为题目保证点权是自然数)
    if (target >= 1) {
        // TODO: 
        // 1. 检查 lookup[u] 中是否有 key 为 target 的记录
        // 2. 如果有,遍历这个 list 中的每一个 pre_edge_id
        // 3. 递归调用:ans = (ans + dfs(pre_edge_id)) % MOD
        vector<int> pre=lookup[u][target];
        if(pre.size()){
            for(auto e:pre){
                ans=(ans+dfs(e))%MOD;
            }
        }
    }

    // 步骤 6: 记录并返回
    return memo[edge_idx] = ans;
}

void solve() {
    int n, m;
    cin >> n >> m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        lookup[i].clear();
    }
    all_edges.clear(); // 清空上一轮的边
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        all_edges.push_back({u, v, i});
        lookup[v][a[u]].push_back(i);
        // 初始化 memo
        memo[i] = -1;
    }

    long long total_ans = 0;
    // TODO: 遍历所有边 (0 到 m-1),累加 dfs(i) 的结果,记得取模
    for(int i=0;i<m;i++){
        total_ans=(total_ans+dfs(i))%MOD;
    }    
    cout << total_ans << "\n";
}

int main() {
    // 加速输入输出
    ios::sync_with_stdio(false);
    cin.tie(0);

    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

Starsilk

#include <bits/stdc++.h>
using namespace std;
const long long mod=998244353;
long long a[200000];
map<long long,long long>dp[200000];
struct linex{
    int u;
    int v;
    long long w;
    friend bool operator<(linex a,linex b){
        return a.w<b.w;
    }
}lx[200000];
int main(){
    ios::sync_with_stdio(false),cin.tie(0);
    int T,n,m,i;
    long long ans,x;
    for(cin>>T;T>0;T--)
    {
        cin>>n>>m;
        for(i=0;i<n;i++)cin>>a[i];
        for(i=0;i<m;i++)
        {
            cin>>lx[i].u>>lx[i].v;
            lx[i].u--;
            lx[i].v--;
            lx[i].w=a[lx[i].u]+a[lx[i].v];
        }
        sort(lx,lx+m);
        ans=0;
        for(i=0;i<m;i++)
        {
            x=dp[lx[i].u][a[lx[i].v]];
            ans=(ans+x+1)%mod;
            dp[lx[i].v][a[lx[i].v]+a[lx[i].u]]=(dp[lx[i].v][a[lx[i].v]+a[lx[i].u]]+x+1)%mod;
        }
        for(i=0;i<n;i++)dp[i].clear();
        cout<<ans<<'\n';
    }
    return 0;
}

真的折磨了我半天,暂时不想看到你了,我写简单题找找自信去(抄AC 代码)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
const int N=200000;

struct node{
    int u,v,w;
    friend bool operator<(node a,node b){
        return a.w<b.w;
    }
};

map<int,int> f[N];
void solve(){
    int n,m;
    cin>>n>>m;
    vector<int> a(n);
    vector<node> lx(m);
    for(int i=0;i<n;i++) cin>>a[i];
    for(int i=0;i<m;i++){
        cin>>lx[i].u>>lx[i].v;
        lx[i].u--;
        lx[i].v--;
        lx[i].w=a[lx[i].u]+a[lx[i].v];
    }
    sort(lx.begin(),lx.end());

    int ans=0;
    for(int i=0;i<m;i++){
        int x=f[lx[i].u][a[lx[i].v]];
        ans=(ans+1+x)%mod;
        f[lx[i].v][a[lx[i].u]+a[lx[i].v]]=(f[lx[i].v][a[lx[i].u]+a[lx[i].v]]+x+1)%mod;
    }

    for(int i=0;i<n;i++) f[i].clear();
    cout<<ans<<endl;
    return ;
}


signed main(){
    int t;
    cin>>t;
    while(t--){
        solve();
    }
    return 0;
}