题解 | #基因调控网络#
基因调控网络
https://www.nowcoder.com/practice/ef455ef70e704e4d926538de1cf242b2
题目链接
题目描述
在一个基因调控网络(有向图)中,寻找一种名为“协同调控模体”的结构数量。
一个协同调控模体由四个不同的基因  组成,它们满足以下激活关系:
- 主调节基因 
能直接激活两个中间基因
和
(即存在边
和
)。
 - 这两个中间基因 
和
又都能直接激活同一个目标基因
(即存在边
和
)。
 
简单来说,该结构是一个“菱形”:。
解题思路
这是一个在有向图中对特定子图进行计数的问题。暴力枚举所有四个节点的组合 () 显然会超时,我们需要一个更高效的算法。
1. 问题转换
协同调控模体的结构核心是:存在一个主调节基因  和一个目标基因 
,并且至少有两条从 
 到 
 的、经过不同中间基因的、长度为 2 的路径。
例如,路径  和 
 就构成了这样一个模体。如果有三条这样的路径(例如,通过 
),则可以构成三个模体(
 组合, 
 组合, 
 组合)。
2. 高效计数算法
基于上述观察,我们可以设计一个通过计算“长度为 2 的路径”来解决问题的算法。
- 
核心思想: 遍历每一个基因,把它当作模体中的主调节基因
。然后,对于这个固定的
,我们去计算它能通过不同的中间基因形成多少个模体。
 - 
算法步骤:
- 预处理:首先,根据输入构建图的邻接表 
adj,其中adj[u]存储了所有基因u能直接激活的基因列表。 - 主循环:遍历每一个基因 
a(从 1 到),将其视为潜在的主调节基因
。
 - 寻找两步路径:对于当前的基因 
a,我们需要找到所有可以从它出发、走两步到达的基因。- 我们使用一个哈希表 (Map) 
paths2_count来统计。paths2_count[d]的值将记录从a到d的长度为 2 的路径有多少条。 - 遍历 
a的所有邻居b(即a -> b)。 - 再遍历 
b的所有邻居d(即b -> d)。 - 每找到一条路径 
a -> b -> d,我们就将paths2_count[d]的计数值加 1。 
 - 我们使用一个哈希表 (Map) 
 - 计算组合:当遍历完 
a的所有两步可达路径后,我们检查paths2_count这个哈希表。- 对于表中的每一个条目 
(d, k),其中k是paths2_count[d]的值。 - 这个 
k意味着,从a到d存在条不同的长度为 2 的路径,它们分别经过
个不同的中间基因。
 - 从这 
个中间基因中任意选择两个,就可以与
a和d构成一个协同调控模体。选择的方法数是组合数。
 
 - 对于表中的每一个条目 
 - 累加结果:将计算出的组合数累加到总数 
total_motifs中。 - 循环与返回:当外层循环遍历完所有可能的基因 
a后,total_motifs就是最终的答案。 
 - 预处理:首先,根据输入构建图的邻接表 
 - 
细节:节点不同 题目要求四个基因
互不相同。在我们的算法中:
是通过组合数
保证的(从
个不同中间基因中选两个)。
等关系在没有自环的图中是天然成立的。
- 唯一需要注意的是 
不能等于
。因此,在找到路径
a -> b -> d后,我们需要确保a != d才进行计数。 
 
代码
#include <iostream>
#include <vector>
#include <map>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    int n, m;
    cin >> n >> m;
    // 使用邻接表表示图
    vector<vector<int>> adj(n + 1);
    for (int i = 0; i < m; ++i) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
    }
    long long total_motifs = 0;
    // 遍历每个基因作为主调节基因 A
    for (int a = 1; a <= n; ++a) {
        // paths2_count[d] 记录从 a 到 d 长度为2的路径数量
        map<int, int> paths2_count;
        // 遍历 A 的下游基因 B
        for (int b : adj[a]) {
            // 遍历 B 的下游基因 D
            for (int d : adj[b]) {
                // 确保 A 和 D 不同
                if (a != d) {
                    paths2_count[d]++;
                }
            }
        }
        // 计算组合数
        for (auto const& [d, k] : paths2_count) {
            if (k >= 2) {
                total_motifs += (long long)k * (k - 1) / 2;
            }
        }
    }
    cout << total_motifs << "\n";
    return 0;
}
import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        // 使用邻接表表示图
        List<List<Integer>> adj = new ArrayList<>();
        for (int i = 0; i <= n; i++) {
            adj.add(new ArrayList<>());
        }
        for (int i = 0; i < m; i++) {
            int u = sc.nextInt();
            int v = sc.nextInt();
            adj.get(u).add(v);
        }
        long totalMotifs = 0;
        // 遍历每个基因作为主调节基因 A
        for (int a = 1; a <= n; a++) {
            // paths2Count.get(d) 记录从 a 到 d 长度为2的路径数量
            Map<Integer, Integer> paths2Count = new HashMap<>();
            // 遍历 A 的下游基因 B
            for (int b : adj.get(a)) {
                // 遍历 B 的下游基因 D
                for (int d : adj.get(b)) {
                    // 确保 A 和 D 不同
                    if (a != d) {
                        paths2Count.put(d, paths2Count.getOrDefault(d, 0) + 1);
                    }
                }
            }
            // 计算组合数
            for (int k : paths2Count.values()) {
                if (k >= 2) {
                    totalMotifs += (long) k * (k - 1) / 2;
                }
            }
        }
        System.out.println(totalMotifs);
    }
}
import collections
def main():
    n, m = map(int, input().split())
    # 使用邻接表表示图
    adj = collections.defaultdict(list)
    for _ in range(m):
        u, v = map(int, input().split())
        adj[u].append(v)
    total_motifs = 0
    # 遍历每个基因作为主调节基因 A
    for a in range(1, n + 1):
        # paths2_count[d] 记录从 a 到 d 长度为2的路径数量
        paths2_count = collections.defaultdict(int)
        # 遍历 A 的下游基因 B
        for b in adj[a]:
            # 遍历 B 的下游基因 D
            for d in adj[b]:
                # 确保 A 和 D 不同
                if a != d:
                    paths2_count[d] += 1
        
        # 计算组合数
        for k in paths2_count.values():
            if k >= 2:
                total_motifs += k * (k - 1) // 2
    print(total_motifs)
if __name__ == "__main__":
    main()
算法及复杂度
- 算法: 图遍历, 哈希表计数
 - 时间复杂度: 
,其中
是边的集合,out-degree(
) 是节点
的出度。该复杂度的本质是遍历图中所有长度为 2 的路径,对于稀疏图而言,这远优于
。
 - 空间复杂度: 
,主要用于存储图的邻接表。在每次主循环中,哈希表的空间消耗最多为
。
 
查看16道真题和解析