找回密码
 立即注册
查看: 33|回复: 0

【NOIP提高组2003】神经网络(洛谷P1038)题解:拓扑排序与动态规划的应用

[复制链接]
  • 打卡等级:常驻代表
  • 打卡总天数:33
  • 打卡月天数:2
  • 打卡总奖励:223
  • 最近打卡:2025-08-14 15:12:36

39

主题

1

回帖

4597

积分

VIP会员

积分
4597
发表于 2025-7-17 15:41:37 | 显示全部楼层 |阅读模式
一、题目解读
2003年NOIP提高组中的“神经网络”题目(洛谷P1038)要求处理一个由神经元和带权有向边构成的网络。题目给定神经元的初始状态、阈值,以及神经元之间的连接关系,需要模拟信号传递过程,并输出最终状态。核心在于解决信号传递的顺序和条件判断,涉及到图论算法的应用。
二、解题思路
采用拓扑排序为核心思路,将神经网络抽象为有向无环(DAG)。关键在于处理节点的入度和状态更新:
1. 利用拓扑排序确定节点处理顺序,保证每个节点仅在其所有前驱节点处理后被访问;
2. 通过邻接表存储边信息,动态维护节点的入度和状态;
3. 节点传递信号的条件是其状态大于阈值,且传递权重影响后续节点状态。
三、解题步骤
1. 数据输入与初始化:读入神经元数量n、边数p,初始化神经元状态、阈值及邻接表;
2. 构建图结构:根据边信息更新入度,标记输出层节点;
3. 拓扑排序预处理:将入度为0的节点加入队列,并调整非输入层节点状态(减去阈值);
4. 拓扑排序迭代
○ 弹出队首节点,若状态≤0则不传递信号;
○ 否则向邻接点传递信号(状态加权更新),并递减其入度,若入度为0则加入队列;
5. 输出结果:最终状态即为输出层节点的状态。
四、代码与注释
  1. #include <iostream>
  2. #include <vector>
  3. #include <queue>
  4. using namespace std;

  5. // 定义神经元结构体
  6. struct Neuron {
  7.     int c;      // 当前状态
  8.     int u;      // 阈值
  9.     int in_degree;  // 入度
  10.     bool is_output; // 是否为输出层神经元
  11. };

  12. int main() {
  13.     int n, p;
  14.     cin >> n >> p;
  15.    
  16.     vector<Neuron> neurons(n+1); // 神经元数组,从1开始编号
  17.     vector<vector<pair<int, int>>> adj(n+1); // 邻接表,存储边和权重
  18.    
  19.     // 输入神经元初始状态和阈值
  20.     for (int i = 1; i <= n; ++i) {
  21.         cin >> neurons[i].c >> neurons[i].u;
  22.         neurons[i].is_output = true; // 初始假设所有神经元都是输出层
  23.     }
  24.    
  25.     // 输入边信息并构建邻接表
  26.     for (int i = 0; i < p; ++i) {
  27.         int from, to, w;
  28.         cin >> from >> to >> w;
  29.         adj[from].emplace_back(to, w);
  30.         neurons[to].in_degree++; // 目标节点入度增加
  31.         neurons[from].is_output = false; // 有出边的节点不是输出层
  32.     }
  33.    
  34.     // 拓扑排序队列,初始化时加入所有入度为0的节点
  35.     queue<int> q;
  36.     for (int i = 1; i <= n; ++i) {
  37.         if (neurons[i].in_degree == 0) {
  38.             q.push(i);
  39.         } else {
  40.             // 非输入层神经元需要减去阈值
  41.             neurons[i].c -= neurons[i].u;
  42.         }
  43.     }
  44.    
  45.     // 拓扑排序处理
  46.     while (!q.empty()) {
  47.         int current = q.front();
  48.         q.pop();
  49.         
  50.         // 如果当前神经元状态<=0,不传递信号
  51.         if (neurons[current].c <= 0) {
  52.             for (auto &edge : adj[current]) {
  53.                 int next = edge.first;
  54.                 if (--neurons[next].in_degree == 0) {
  55.                     q.push(next);
  56.                 }
  57.             }
  58.             continue;
  59.         }
  60.         
  61.         // 向所有邻接神经元传递信号
  62.         for (auto &edge : adj[current]) {
  63.             int next = edge.first;
  64.             int weight = edge.second;
  65.             neurons[next].c += weight * neurons[current].c;
  66.             
  67.             // 入度减为0时加入队列
  68.             if (--neurons[next].in_degree == 0) {
  69.                 q.push(next);
  70.             }
  71.         }
  72.     }
  73.    
  74.     // 收集输出层神经元结果
  75.     bool has_output = false;
  76.     for (int i = 1; i <= n; ++i) {
  77.         if (neurons[i].is_output && neurons[i].c > 0) {
  78.             cout << i << " " << neurons[i].c << endl;
  79.             has_output = true;
  80.         }
  81.     }
  82.    
  83.     if (!has_output) {
  84.         cout << "NULL" << endl;
  85.     }
  86.    
  87.     return 0;
  88. }
复制代码


五、总结
该解法巧妙将神经网络转化为拓扑排序问题,通过动态规划思想维护节点状态,避免了复杂的递归深度优先搜索。关键在于利用入度控制节点处理顺序,确保信号传递的正确性。代码简洁高效,是解决此类图论问题的经典范例。
来源:CSP题解

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表