202109 CSP认证 | 脉冲神经网络

news2025/2/28 17:28:07

3. 脉冲神经网络
好久之前第一次写的时候完全对第三题没感觉,提交上去得了个0 分…
这次自己再写了一遍,花的时间不多,写的时候感觉逻辑也不是特别难。最后是超时了,感觉第三题开始涉及到优化了,不仅仅是暴力模拟就可以拿分了,下面先贴上自己写的 66 分代码

#include<bits/stdc++.h>
using namespace std;
const int M = 100010;
//const int M = 100;
int N, S, P, T; //N个神经元, S个突触, P个脉冲源, T时刻
double deltaT;  //间隔时间

struct cell{
    double u, v;
    double a, b, c, d;
};
struct edge{
    int from;
    int to;
    double w;
    int D;
};
double r[2 * M];   //存放脉冲源的r信息
cell neuron[M];   //定义一个神经元数组
edge synapse[M];  //定义一个脉冲数组

double timePulse[M][1010];  //在每个时刻,哪些神经元收到了多少信号
unordered_set<int> sendPulse;  //记录当前时刻会发送脉冲的编号
int res[M];

static unsigned long next_1 = 1;

/* RAND_MAX assumed to be 32767 */
int myrand(void) {
    next_1 = next_1 * 1103515245 + 12345;
    return((unsigned)(next_1/65536) % 32768);
}


int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> N >> S >> P >> T >> deltaT;
    int cnt = 0, Rn;

    while(cnt < N){
        double v, u, a, b, c, d;
        cin >> Rn >> v >> u >> a >> b >> c >> d;
        for(int i = cnt; i < cnt + Rn; i ++){
            neuron[i].a = a; neuron[i].b = b; neuron[i].c = c; neuron[i].d = d;
            neuron[i].v = v; neuron[i].u = u;
        }
        cnt += Rn;
    }
    for(int i = 0;i < P; i ++){   //输入脉冲源信息
        cin >> r[i + N];
    }
    for(int i = 0;i < S; i ++){  //输入突触信息
        cin >> synapse[i].from >> synapse[i].to >>synapse[i].w >> synapse[i].D;
    }

    double MAXV = -1 * 0x3f3f3f3f, MINV = 0x3f3f3f3f;

    for(int t = 1;t <= T; t ++){
        sendPulse.clear();
        for(int i = 0;i < P; i ++){
            int rand = myrand();
            //cout << rand << "\n";
            if(r[i + N] > rand){  //该脉冲源在该时刻将发送脉冲
                sendPulse.insert(i + N);
            }
        }

        for(int i = 0;i < N;i ++){
            double Ik = timePulse[t][i];
            double cur_v = neuron[i].v;
            double cur_u = neuron[i].u;
            double a = neuron[i].a, b = neuron[i].b, c = neuron[i].c, d = neuron[i].d;
            double v, u;

            v = cur_v + deltaT * (0.04*cur_v*cur_v+5*cur_v+140-cur_u) + Ik;
            u = cur_u + deltaT * a * (b * cur_v - cur_u);
            if(v >= 30){
                sendPulse.insert(i);
                res[i] ++;
                v = c;
                u += d;
            }

            if(t == T){
                MAXV = max(v, MAXV);
                MINV = min(v, MINV);
            }
            //cout << v <<"\n";
            neuron[i].v = v;  neuron[i].u = u;
        }

        for(int i = 0;i < S; i ++){
            int from = synapse[i].from, to = synapse[i].to, D = synapse[i].D;
            double w = synapse[i].w;
            if(sendPulse.count(from)){  //输入端接收到脉冲
                timePulse[t + D][to] += w;
            }
        }
    }

    int MAXS = 0, MINS = 0x3f3f3f3f;
    for(int i = 0;i < N;i ++){
        if(res[i] > MAXS) MAXS = res[i];
        if(res[i] < MINS) MINS = res[i];
    }

    cout << fixed << setprecision(3) << MINV << " " << MAXV << "\n" << MINS << " " << MAXS;
    return 0;
}

感觉本题很多人都卡了66分,代码逻辑上基本相同,满分代码和66分代码的优化主要在以下两点

  • 时间优化(针对卡常):在时间的循环中,对各个脉冲源和神经元的循环是不可省略的,这里涉及到计算。因此进行的优化就是更改突触的存储结构,采用邻接表的形式进行存储,此时若计算出当前节点将发出脉冲后,所有与该节点有突触连接的出节点立马进行更新。此时会减少一次10^3的遍历时间。
  • 空间优化:在进行时间优化后,会发现仍然超时,且出现空间过大的情况,如下图所示。所以在这里将进行空间的优化,主要是针对于timePluse[][]这个存储数组的优化)
    在这里插入图片描述

用邻接表来存储突触边:采用数组的方式来模拟邻接表的存储
可参考我之前写的一个链接:图论堆优化

邻接表声明:

int h[N], e[M], w[M], ne[M], idx;  //N为节点个数,M为边的个数
其中
h[a] 指向a节点起点的邻接表列表的最后一个元素
e[idx]  为当前idx编号的边指向的节点
w[idx]  为当前idx编号的边的权重
ne   存储邻接表链表,当前值对于邻接表下一个的地址,类似于值为指针

初始化
idx = 0;
memset(h, -1, sizeof h);

邻接表构建

void add(int a, int b, int c) {
    e[idx] = b;
    w[idx] = c;   
    //以上的两步构建了一条边,这条边只存储了终点和权值(因为邻接表的表头就是起点,所以不存储无用信息)
    ne[idx] = h[a];  //类似于头插法 node->next = L->next(当前节点的next指针指向头节点的下一个节点)
    h[a] = idx ++;   //L->next = node; idx++ 将当前的头节点的next节点设置为当前节点
 }
 
1. 在这种构建方式中, idx为边的序号,作为边的唯一标识,找到了idx就可以读到该条边的终点信息和权值信息
2. 邻接表的构造方式和头插法类似,h[a]指向最新录入的以a节点为起点的边的信息,其存储的值是一个边的idx,通过ne[idx]可以获得当前边的下一条的边的idx值;ne[idx] == -1 与 node->next == NULL类似,此时遍历到了该链表的尾部节点
3. 如果指向下一个为空时,指针值为-1

邻接表遍历

for(int i = h[vel]; i != -1; i = ne[i]) { 
    //TODO
}
 
i = ne[i] 模拟链表指针的next操作
h[vel] 指向vel链表的最后一个,遍历是从后往前的

此时可以创建一个用来存储突触信息的邻接表,其中以h[idx]来获取与编号为idx的脉冲源或者神经元 相连接的神经元编号,以idx = ne[idx]来模拟指针的移动获取更多的出结点神经元编号。当idx == -1时,也就是相当于表中指针next == nullptr,也即已经移动到尾部,此时即可退出循环

在代码中,每当r[i + N] > rand或者v >= 30,也即当前脉冲源或者神经元将发出脉冲。以i + N / iidx开始遍历邻接表,找到所有与之链接的节点timePluse[当前时间+延迟时间][出结点编号] += 输出脉冲值

引入mod来减少timePluse[][]的存储空间
在66分代码中,timePluse[][]的规模是1e5 x 1e3(T的规模 * 神经元的规模)此时会导致空间内存不足。在参考满分代码中,引入了滚动数组的概念

在本题的滚动数组我的理解是:为了减少规模,我们需要将T的时间进行分组,用分组规模来替换T的规模。同时要保证这个分组规模足够满足神经元和脉冲源 在当前时刻的 读取以及存储要求。比较像背包问题里面的设计,只保存在当前任务下,我会用到的信息量(以这个信息量的跨度为划分依据,比如背包问题就只保留了上一层的信息)

这里的mod = max(D[i]),也即mod取值为所有突触的最大时间间隔加一

在这个划分下,
原本存储时我的第一维度为当前时间加上该突触的时间间隔,此时修改为(k + D[i]) % mod. 其中k = t % mod (k >= 1 and k <= mod). 则修改后的时间会落入到划分长度为mod的时间段中的 k 的后方(此时和当前时刻划分在同一个时间段),或者k的前方(此时为当前时刻划分段的下一个时间段)==>但没关系!!因为当 当前时刻已经到 k 时,小于 k 的部分的数据已经读取并利用在神经元的计算中了,因此虽然在一个数组中以 k 为分界线并不在一个时间段内(就理解为不在一个维度里面吧)但是在使用中并不会有混淆,因为前一部分数据在本时段内也不会再次使用了
其实也即,以当前时刻k为划分,后半部分数据用于读取(直接读取 || 存储后!当前时刻扫描到时,又再次读取); 而前半部分只可能用于存储(用于后一时间段的读取)

下为满分代码:

#include<iostream>
#include<iomanip>
#include<algorithm>
#include<cstring>
using namespace std;
const int M = 2010;
int N, S, P, T; //N个神经元, S个突触, P个脉冲源, T时刻
double deltaT;  //间隔时间

struct cell{
    double u, v;
    double a, b, c, d;
};
double r[M];  //存放脉冲源的信息
cell neuron[M / 2];  //存放神经元的信息
double timePulse[1024][M / 2];  //在每个时刻,哪些神经元收到了多少信号
int res[M / 2];

//用邻接表的形式存储脉冲信息
double w[M / 2];
int h[M], e[M / 2], D[M / 2], ne[M / 2], idx = 0;
static unsigned long next_1 = 1;

/* RAND_MAX assumed to be 32767 */
int myrand(void) {
    next_1 = next_1 * 1103515245 + 12345;
    return((unsigned)(next_1/65536) % 32768);
}

void add(int from, int to, double ww, int dt)
{
    w[idx] = ww;
    e[idx] = to;
    D[idx] = dt;

    ne[idx] = h[from];
    h[from] = idx;
    idx ++;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    memset(h, -1, sizeof h);

    cin >> N >> S >> P >> T >> deltaT;
    int cnt = 0, Rn;

    while(cnt < N){
        double v, u, a, b, c, d;
        cin >> Rn >> v >> u >> a >> b >> c >> d;
        for(int i = cnt; i < cnt + Rn; i ++){
            neuron[i].a = a; neuron[i].b = b; neuron[i].c = c; neuron[i].d = d;
            neuron[i].v = v; neuron[i].u = u;
        }
        cnt += Rn;
    }
    for(int i = 0;i < P; i ++){   //输入脉冲源信息
        cin >> r[i + N];
    }

    int mod = 0;
    for(int i = 0;i < S; i ++){  //输入突触信息
        int from, to, dt;
        double ww;
        cin >> from >> to >> ww >> dt;
        add(from, to, ww, dt);
        mod = max(mod, dt + 1);
    }

    double MAXV = -1 * 0x3f3f3f3f, MINV = 0x3f3f3f3f;

    for(int k = 1;k <= T; k ++){
        int t = k % mod;
        for(int i = 0;i < P; i ++){
            int rand = myrand();
            //cout << rand << "\n";
            if(r[i + N] > rand){  //该脉冲源在该时刻将发送脉冲
                for(int j = h[i + N];j != -1; j = ne[j]){
                    int to = e[j];
                    timePulse[(t + D[j]) % mod][to] += w[j];
                }
            }
        }

        for(int i = 0;i < N;i ++){
            double Ik = timePulse[t][i];
            double cur_v = neuron[i].v;
            double cur_u = neuron[i].u;
            double a = neuron[i].a, b = neuron[i].b, c = neuron[i].c, d = neuron[i].d;
            double v, u;

            v = cur_v + deltaT * (0.04*cur_v*cur_v + 5*cur_v + 140 -  cur_u) + Ik;
            u = cur_u + deltaT * a * (b * cur_v - cur_u);
            if(v >= 30){
                for(int j = h[i];j != -1; j = ne[j]){
                    int to = e[j];
                    timePulse[(t + D[j]) % mod][to] += w[j];
                }
                res[i] ++;
                v = c;
                u += d;
            }

            if(k == T){
                MAXV = max(v, MAXV);
                MINV = min(v, MINV);
            }
            //cout << v <<"\n";
            neuron[i].v = v;  neuron[i].u = u;
        }

        memset(timePulse[t], 0, sizeof timePulse[t]);

    }

    int MAXS = 0, MINS = 0x3f3f3f3f;
    for(int i = 0;i < N;i ++){
        if(res[i] > MAXS) MAXS = res[i];
        if(res[i] < MINS) MINS = res[i];
    }

    cout << fixed << setprecision(3) << MINV << " " << MAXV << "\n" << MINS << " " << MAXS;
    return 0;
}

这里还有一个点,时间卡的实在是太严了,所以在这里用万能头文件反而会加重编译过程中的耗时。所以修改了头文件部分

over!! 难,实在是难(苦涩.jpg)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1504392.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阿里云服务器“地域”是啥?咋选合适?

阿里云服务器地域选择方法&#xff0c;如何选择速度更快、网络延迟更低的地域节点&#xff0c;地域指云服务器所在的地理位置区域&#xff0c;地域以城市划分&#xff0c;如北京、杭州、深圳及上海等&#xff0c;如何选择地域&#xff1f;建议根据用户所在地区就近选择地域&…

市场情绪主升周期的分歧产生核心节点剖析

昨天下午我在群里分享了核心一些观点&#xff1a; 理解市场&#xff0c;划分情绪周期阶段&#xff0c;本质还是理解&#xff0c;观察驱动市场先手资金的动向。 亏钱可以说是因为我们带有个人偏见导致的&#xff0c;进一步说是因为我们偏离了市场资金共识导致的&#xff0c;可能…

java016 - Java多态

1、概述 同一个对象&#xff0c;不同的时刻&#xff0c;表现出来不同的形态。 2、多态成员的访问特点 成员变量&#xff1a;编译看左边&#xff0c;执行看左边。 3、多态的优缺点 代码&#xff1a; 动物类&#xff1a; 猫类&#xff1a; 操作类&#xff1a; 测试1类&#x…

第五十四回 高太尉大兴三路兵 呼延灼摆布连环马-AI通过构建并训练CNN网络来进行飞机识别

呼延灼举荐了百胜将韩滔和天目将彭玘做先锋。 两军对战&#xff0c;韩滔和秦明斗二十回合&#xff0c;呼延灼与林冲斗在一起&#xff0c;花荣与彭玘斗在一处&#xff0c;后彭玘与一丈青扈三娘斗在一起&#xff0c;被扈三娘抓住。 尽管梁山占优&#xff0c;宋江也没有乘胜追击&…

【Linux】Linux——Centos7安装

目录 虚拟机安装【空壳子】安装VMware Workstation新建虚拟机硬件兼容性(直接下一步)稍后安装操作系统客户及操作系统选择Linux&#xff0c;版本Centos764位给虚拟机命名&#xff0c;并选择安装位置处理器配置&#xff08;默认即可&#xff0c;不够用后面可以调&#xff09;虚拟…

Spring bean的生命周期图解(转)

转载自&#xff1a; 生命周期详解

安装nginx:手动安装和yum安装

本文在centos7.9下分别尝试了yum安装和手动安装&#xff0c;记录一下试验过程。为后来者少踩点坑。 下载 下载地址&#xff1a;链接 。建议下载稳定版本&#xff0c;也就是Stable Version&#xff0c;这里下载的是 nginx-1.24.0 # 我下载在如下文件夹 mkdir/opt/apps cd /op…

美食网页成品 HTML美食网页设计制作 前端美食网页开发 热门美食特产网页制作中国传统特色小吃-臭豆腐 6页面 美食主题 HTML5 带设计说明

美食网页成品 HTML美食网页设计制作 前端美食网页开发 热门美食特产网页制作 中国传统特色小吃-臭豆腐 6页面 美食主题 HTML5 带设计说明 http://www.yuanle.net.cn/anli/30/4138.html

【附教程】2024,人工智能+AI绘画,看这里就够了~14款主流图像生成软件工具总有一个适合你

AI绘画技术通过深度学习和处理海量图像数据&#xff0c;能够迅速将文字描述转化为富有创意和艺术性的画作。这一技术不仅极大地提升了艺术家的创作效率和作品质量&#xff0c;还为他们提供了全新的灵感来源和创作方式&#xff0c;推动了艺术领域的创新与发展。 同时&#xff0…

Java高频面试之基础篇

有需要互关的小伙伴,关注一下,有关必回关,争取今年认证早日拿到博客专家 解释下什么是面向对象&#xff1f;面向对象和面向过程的区别&#xff1f; 面向对象的三大特性&#xff1f;分别解释下&#xff1f; 封装 继承 多态 JDK、JRE、JVM 三者之间的关系&#xff1f; JVM…

AI代码提示工具

1. 介绍 之前一直用国外的Github copilar代码指示工具&#xff0c;写代码效率提高了30%-50%&#xff0c;但是这个工具需要付费而且有时候出现连接问题&#xff0c;后来发现国内也有一款相同的代码只能提示工具&#xff0c;我们只需要在Vscode或者JetBrains里面安装这个插件&am…

Python 创建PPT

本篇为如何使用Python来创建ppt文件。 创建PPT 安装必要的库 命令如下&#xff1a; pip install python-pptx 安装过程&#xff1a; 创建ppt文件 在当前目录下创建一个test的ppt文件。其中包含两页&#xff0c;分别使用了不同的布局。 第一页设置了标题和内容。第二页只设…

flink重温笔记(十三): flink 高级特性和新特性(2)——ProcessFunction API 和 双流 join

Flink学习笔记 前言&#xff1a;今天是学习 flink 的第 13 天啦&#xff01;学习了 flink 高级特性和新特性之ProcessFunction API 和 双流 join&#xff0c;主要是解决大数据领域数据从数据增量聚合的问题&#xff0c;以及快速变化中的流数据拉宽问题&#xff0c;即变化中多个…

使用51单片机控制lcd1602字体显示

部分效果图&#xff1a; 准备工作&#xff1a; 51单片机&#xff08;BST&#xff09;1602显示屏 基础知识&#xff1a; 注&#xff1a;X表示可以是0&#xff0c;也可以是1&#xff1b; DL 1&#xff0c; N 1&#xff0c; F 0&#xff0c; 代码一&#xff1a; 要求显示字母…

【力扣白嫖日记】1164.指定日期的产品价格

前言 练习sql语句&#xff0c;所有题目来自于力扣&#xff08;https://leetcode.cn/problemset/database/&#xff09;的免费数据库练习题。 今日题目&#xff1a; 1164.指定日期的铲平价格 表&#xff1a;Products 列名类型product_idintnew_priceintchange_datedate (pr…

如何进入Windows 11的安全模式?这里提供详细步骤

序言 如果你在启动Windows 11 PC时遇到问题,则重新启动到安全模式可能会有所帮助,该模式会暂时禁用驱动程序和功能,以使你的PC更稳定。下面是如何做到这一点。 在Windows 7和更早版本中,通常可以在打开电脑后按功能键(如F8)启动安全模式。Microsoft从Windows 8中删除了…

CSS3基础2

CSS3 用户界面 resize 示例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title>…

基于Spring Boot+Vue的论坛网站

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是墨韵&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c;不进则退。学习如赶路&#xff0c;不能慢一步。 目录 一、项目简介 二、开发技术与环…

回溯算法09-子集II(Java/子集问题的去重方法)

9.子集II 题目描述 给你一个整数数组 nums &#xff0c;其中可能包含重复元素&#xff0c;请你返回该数组所有可能的子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。返回的解集中&#xff0c;子集可以按 任意顺序 排列。 示例 1&#xff1a; 输入&#xf…

PyQt5实现远程更新exe可执行文件

PyQt5实现远程下载更新exe可执行文件 1、实现流程 1、获取远程http地址 2、获取需要更新的exe文件 3、点击更新 4、把exe强关闭 5、下载文件 6、更新2、效果图 3、示例代码 conf.ini配置文件&#xff1a; {"http_address_edit_value": "http://xxx.com/xxx/…