跳转至

202109-3 脉冲神经网络

70% 数据——模拟

先不考虑具体时间复杂度,思考朴素做法应该怎么做:

  1. 枚举 \([0,T]\) 每个时刻,这里应为一个外层循环;
  2. 对脉冲源进行处理:通过 \(r\) 参数判断是否会发射脉冲;
  3. 对神经元进行处理:通过 \(v_k\) 判断是否会发射脉冲;如果发射脉冲,则更新 \(u_k,v_k\)

判断脉冲源是否发射脉冲相对容易。但对于神经元来说,由于传播延迟的存在,我们需要知道 \(I_k\), 即神经元在 \(k\) 时刻接收到所有脉冲输入的强度之和。 又由于每个神经元是否发射脉冲是互相独立的,我们需要知道每个神经元每个时刻接收到的脉冲和。

一种思路是开一个 \(\mathrm{O}(NT)\) 的二维数组来记录。由于空间大小的限制,我们只能通过 \(70%\) 数据。

再来考虑时间复杂度:枚举时刻是 \(\mathrm{O}(T)\),枚举脉冲、神经元及发射脉冲需要 \(\mathrm{O}(N+S+P)\), 整体复杂度 \(\mathrm{O}(T(N+S+P))\),可以通过所有的数据。

100% 数据——滚动数组优化

为了得到满分,目前的任务是缩小记录每个神经元 \(I_k\) 的空间复杂度。

提示

题目给出的量的大小一般都是有用的。如果没有思路时,可以从这些貌似“没用”的量上进行突破,比如本题的 \(D\)

考虑耗费空间的位置:虽然有传播延迟 \(d\) 的存在,但在某个时刻 \(t\), 能影响到神经元 \(I_k\) 的时刻范围在 \([t, t+\max\{d\}]\) 范围内。 一方面,对于 \(k>t+\max\{d\}\)\(I_k\) 我们没必要记录,因为不会有脉冲对其造成影响; 另一方面,对于 \(k<t\)\(I_k\) 我们没必要保留。 我们只需要维护 \(k\in [t, t+\max\{d\}]\)\(I_k\) 值就可以完成任务。 在这种情况下我们可以利用{\heiti{滚动数组}}进行优化。

滚动数组

滚动数组是一种思想,通过对数组“取余”来缩小空间。设目前需要保留值的个数为 \(p\),则在使用滚动数组后,原数组在 \(i\) 的位置由 \(i \bmod p\) 替代。

不妨设求解原数组中 \(i\) 位置值涉及的位置有 \([i-p, i-1]\), 当计算出原数组 \(i\) 位置的值后,直接覆盖 \(i \bmod p\) 位置的值即可。 因为被覆盖的值为 \(i-p\) 位置的值,在下次计算 \(i+1\) 位置的值时, 需要的区间为 \([i-p+1,i]\),已经不需要存储该位置的值了,所以可以放心覆盖。

在选取合适的 \(p\) 的时候,可以尽量大一些,防止覆盖需要保留的数据。

对于这道题,我们可以利用滚动数组的思路,将空间优化到 \(\mathrm{O}(ND)\),即可通过此题。

代码实现
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
const int maxn = 1010;
const int maxD = 1010;

// 如果使用原题的 next 可能会 CE,这里改成 nxt
static unsigned long nxt = 1;
/* RAND_MAX assumed to be 32767 */
int myrand(void) {
    nxt = nxt * 1103515245 + 12345;
    return ((unsigned)(nxt / 65536) % 32768);
}

int N, S, P, T;
double dt;
// 脉冲源只需要 r 参数即可
int r[maxn];

// 神经元
struct Neuron {
    double v, u, a, b, c, d;
    int activate_times;
} neuron[maxn << 1];

// 突触
struct Synapse {
    int s, t;
    double w;
    int D;
} synapse[maxn << 1];

// 存图
vector<int> G[maxn << 1];
// 存储每个时刻每个节点的 Ik 值
double I[maxD][maxn];

int main() {
    scanf("%d%d%d%d", &N, &S, &P, &T);
    scanf("%lf", &dt);
    int cur = 0, rn;
    double ru, rv, ra, rb, rc, rd;
    while (cur < N) {
        scanf("%d", &rn);
        scanf("%lf%lf%lf%lf%lf%lf", &rv, &ru, &ra, &rb, &rc, &rd);
        while (rn--) {
            neuron[cur].v = rv;
            neuron[cur].u = ru;
            neuron[cur].a = ra;
            neuron[cur].b = rb;
            neuron[cur].c = rc;
            neuron[cur].d = rd;
            neuron[cur].activate_times = 0;
            ++cur;
        }
    }
    for (int i = 0; i < P; ++i) {
        scanf("%d", &r[i]);
    }
    for (int i = 0; i < S; ++i) {
        scanf("%d%d", &synapse[i].s, &synapse[i].t);
        scanf("%lf", &synapse[i].w);
        scanf("%d", &synapse[i].D);
        // 连边
        G[synapse[i].s].push_back(i);
    }

    // 按时间递增
    for (int t = 0; t < T; ++t) {
        // 滚动数组清零
        for (int i = 0; i < N; ++i) {
            I[(t - 1 + maxD) % maxD][i] = 0;
        }
        // 脉冲源
        for (int i = 0; i < P; ++i) {
            if (r[i] > myrand()) {
                // 脉冲激发
                for (int j = 0; j < G[N + i].size(); ++j) {
                    Synapse tmps = synapse[G[N + i][j]];
                    I[(t + tmps.D) % maxD][tmps.t] += tmps.w;
                }
            }
        }
        // 神经元
        for (int i = 0; i < N; ++i) {
            double u = neuron[i].u, v = neuron[i].v;
            neuron[i].v =
                v + dt * (0.04 * v * v + 5 * v + 140 - u) + I[t % maxD][i];
            neuron[i].u = u + dt * neuron[i].a * (neuron[i].b * v - u);
            if (neuron[i].v >= 30) {
                // 脉冲激发
                neuron[i].v = neuron[i].c;
                neuron[i].u += neuron[i].d;
                ++neuron[i].activate_times;
                for (int j = 0; j < G[i].size(); ++j) {
                    Synapse tmps = synapse[G[i][j]];
                    I[(t + tmps.D) % maxD][tmps.t] += tmps.w;
                }
            }
        }
    }

    // 输出结果
    double minv = 1e18, maxv = -1e18;
    int mint = 1e9, maxt = 0;
    for (int i = 0; i < N; ++i) {
        if (neuron[i].v < minv)
            minv = neuron[i].v;
        if (neuron[i].v > maxv)
            maxv = neuron[i].v;
        if (mint > neuron[i].activate_times)
            mint = neuron[i].activate_times;
        if (maxt < neuron[i].activate_times)
            maxt = neuron[i].activate_times;
    }
    printf("%.3lf %.3lf\n%d %d", minv, maxv, mint, maxt);
    return 0;
}

评论