P1038 题解
P1038 [NOIP2003 提高组] 神经网络
题意
在兰兰的模型中,神经网络就是一张有向图,图中的节点称为神经元,而且两个神经元之间至多有一条边相连。
对于第 \(i\) 个神经元来说,\(C_i\) 表示神经元目前的状态,\(U_i\) 是阈值,可视为神经元的一个内在参数。
神经元按一定的顺序排列,构成整个神经网络。在兰兰的模型之中,神经网络中的神经元分为几层;称为输入层、输出层,和若干个中间层。每层神经元只向下一层的神经元输出信息,只从上一层神经元接受信息。
兰兰规定,\(C_i\) 服从公式:
公式中的 \(W_{ji}\)(可能为负值)表示连接 \(j\) 号神经元和 \(i\) 号神经元的边的权值。当 \(C_i\) 大于 \(0\) 时,该神经元处于兴奋状态,否则就处于平静状态。当神经元处于兴奋状态时,下一秒它会向其他神经元传送信号,信号的强度为 \(C_i\)。
现在,给定一个神经网络,及当前输入层神经元的状态(\(C_i\)),要求你的程序运算出最后网络输出层的状态。
思路
在题目中,有这么一个公式:
通过这个公式,我们可以知道,如果想要求解 \(C_i\),就必须先求解出所有的 \(C_j\),并且 \(j\) 到 \(i\) 连了一条边。
那么,这不就是一个标准的拓扑排序 + 递推吗?
先拓扑排序求出拓扑序,再根据拓扑序求出所有 \(C_i\),最后输出即可。
但是,要注意,这个题目有三个坑点:
- 在公式下面,有这么一段话:
-
当 \(C_i\) 大于 \(0\) 时,该神经元处于兴奋状态,否则就处于平静状态。当神经元处于兴奋状态时,下一秒它会向其他神经元传送信号,信号的强度为 \(C_i\)。
-
这里说明,这个公式应该再增加一个条件,所有会被计算的 \(C_j\) 必须大于 0,也就是
-
输入层的神经元是不能减去阈值的!!!
-
题目中有这么一句话:
非输入层的神经元开始时状态必然为 0
但这不代表输入层的神经元开始时状态一定不为 \(0\),这是一个逻辑问题,所以不能直接按照 \(C_i\) 来判断是否为输入层。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, p, c[N], u[N], s[N];
struct Node {
int v, w;
};
vector<Node> g[N];
queue<int> que;
void bfs() { // 求拓扑序
for (int i = 1; i <= n; i++) {
if (!s[i]) {
que.push(i);
}
}
while (!que.empty()) {
int tmp = que.front();
que.pop();
for (auto v : g[tmp]) {
s[v.v]--, c[v.v] += (c[tmp] > 0) * c[tmp] * v.w; // 顺便求出 c[i]
if (!s[v.v]) {
que.push(v.v);
}
}
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> p;
for (int i = 1; i <= n; i++) {
cin >> c[i] >> u[i];
}
while (p--) {
int x, y, w;
cin >> x >> y >> w;
g[x].push_back({y, w}), s[y]++;
}
for (int i = 1; i <= n; i++) {
if (s[i]) {
c[i] -= u[i]; // 先将所有 u[i] 都处理好
}
}
bfs();
bool flag = 0;
for (int i = 1; i <= n; i++) {
if (g[i].empty() && c[i] > 0) { // 输出答案
cout << i << ' ' << c[i] << '\n';
flag = 1;
}
}
if (!flag) {
cout << "NULL";
}
return 0;
}