202109 CSP认证 | 脉冲神经网络

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.hqwc.cn/news/527989.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

查看pip当前关联python版本及位置

好久没用python了&#xff0c;把各种pip指向的环境忘光光啦&#xff0c;这里记录一下查看pip当前关联的python版本及位置的方法&#xff1a; pip -V结果&#xff1a; 我一般不用这个版本的python&#xff0c;去环境变量看了一下&#xff0c;原来是anaconda的Scripts自带pip&a…

VUE_自适应布局lib-flexible+postcss-pxtorem、lib-flexible + postcss-px2rem,nuxt页面自适配

lib-flexible postcss-pxtorem适配 我采用的是flexable.js和postcss-pxtorem。我一开始用的是postcss-px2rem后来发现和nuxt引入公共css的时候发生了冲突所以改用了postcss-pxtorem。 安装依赖 npm i lib-flexible -S npm install postcss-pxtorem --save 1、lib-flexible.…

Spring Boot 面试题及答案整理,最新面试题

Spring Boot中的自动配置是如何工作的&#xff1f; Spring Boot的自动配置是其核心特性之一&#xff0c;它通过以下方式工作&#xff1a; 1、EnableAutoConfiguration注解&#xff1a; 这个注解告诉Spring Boot开始查找添加了Configuration注解的类&#xff0c;并自动配置它们…

突破编程_前端_JS编程实例(目录导航)

1 开发目标 目录导航组件旨在提供一个滚动目录导航功能&#xff0c;使得用户可以方便地通过点击目录条目快速定位到对应的内容标题位置&#xff0c;同时也能够随着滚动条的移动动态显示当前位置在目录中的位置&#xff1a; 2 详细需求 2.1 标题提取与目录生成 组件需要能够自…

Java 中的 File 类常用方法介绍

Java 中的 File 类是 java.io 包的一部分&#xff0c;它提供了丰富的文件操作方法。File 类可以用来表示文件和目录路径名的抽象表示形式&#xff0c;即它可以用来获取文件或目录的属性&#xff0c;也可以用来创建、删除、重命名文件和目录。下面是一些常用的 File 类方法&…

JavaEE+springboot教学仪器设备管理系统o9b00-springmvc

本文旨在设计一款基于Java技术的教学仪器设备销售网站&#xff0c;以提高网站性能、功能完善、用户体验等方面的优势&#xff0c;解决现有教学仪器设备销售网站的问题&#xff0c;并为广大教育工作者和学生提供便捷的教学仪器设备销售渠道。本文首先介绍了Java技术的相关基础知…

深入理解Hive:探索不同的表类型及其应用场景

文章目录 1. 引言2. Hive表类型概览2.1 按照数据存储位置2.2 按照数据管理方式2.3 按照查询优化2.4 按照数据的临时性和持久性 3. 写在最后 1. 引言 在大数据时代&#xff0c;Hive作为一种数据仓库工具&#xff0c;为我们提供了强大的数据存储和查询能力。了解Hive的不同表类型…

【运维】本地部署Gemma模型(图文)

工具简介 我们需要使用到两个工具&#xff0c;一个是Ollama&#xff0c;另一个是open-webui。 Ollama Ollama 是一个开源的大语言平台&#xff0c;基于 Transformers 和 PyTorch 架构&#xff0c;基于问答交互方式&#xff0c;提供大语言模型常用的功能&#xff0c;如代码生…

Day30:安全开发-JS应用NodeJS指南原型链污染Express框架功能实现审计

目录 环境搭建-NodeJS-解析安装&库安装 功能实现-NodeJS-数据库&文件&执行 安全问题-NodeJS-注入&RCE&原型链 案例分析-NodeJS-CTF题目&源码审计 开发指南-NodeJS-安全SecGuide项目 思维导图 JS知识点&#xff1a; 功能&#xff1a;登录验证&…

【数理统计实验(四)】方差分析

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

java之mybatis

准备工作 上面4步骤 XML映射文件 动态SQL

小程序 van-field label和输入框改成上下布局

在组件上面加个样式就行&#xff1a;custom-style"display:block;" <van-field label"备注说明" type"textarea" clearable title-width"100px" custom-style"display:block;" placeholder"请输入" /> …