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)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

纪年哥的文物挽救木牌

左&#xff08;江南制造局&#xff0c;曾国藩书天道酬勤&#xff0c;李鸿章少荃印&#xff0c;光绪三十四年制造&#xff09; 中&#xff08;汉阳兵工厂&#xff0c;民国二十六年制造&#xff0c;公元1937年七月七日&#xff0c;抗日战争全面爆发&#xff09; 右&#xff08;…

二 centos 7.9 磁盘挂载

上一步 一 windso10 笔记本刷linux cent os7.9系统-CSDN博客 笔记本有两个盘,系统装在128G的系统盘上,现在把另外一个盘挂载出来使用 lsblk 发现磁盘已经分好了,直接挂载就好了,参考文章:Centos7.9 挂载硬盘_centos7.9挂载硬盘-CSDN博客 永久挂载 lsblk -f分区格式化 mkfs…

upload-labs通关记录

文章目录 前言 1.pass-012.pass-023.pass-034.pass-045.pass-056.pass-067.pass-078.pass-089.pass-0910.pass-1011.pass-1112.pass-1213.pass-1314.pass-1415.pass-1516.pass-1617.pass-1718.pass-1819.pass-19 前言 本篇文章记录upload-labs中&#xff0c;所有的通过技巧和各…

首席翻译张璐老师,今年见不到了

她是我的偶像&#xff0c;张璐&#xff0c;连续多年在重量级会议上担任翻译。 2010年&#xff0c;张璐作为翻译出现&#xff0c;是五年来国家级媒体发布会首次起用女翻译。 2011年3月14日的媒体发布会。张璐再任会议翻译。 2012年的媒体发布会&#xff0c;张璐任翻译。 2013年&…

制定一份完美的测试计划,让您的产品质量更上一层楼!

大家好&#xff0c;我是彭于晏。今天学习测试计划如何书写。 虽然很多人日常工作中都知道测试计划是什么&#xff0c;但是写好测试计划&#xff0c;其实并不容易。今天就来一起学习下测试计划如何书写。 什么是测试计划&#xff1f; 测试计划是一份为软件产品所准备的详细文档…

帮管客CRM jiliyu接口存在SQL漏洞 附POC软件

免责声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用。 1. 帮管客CRM简介 微信公众号搜索:南风漏洞复现文库…

yolo模型中神经节点Mul与Sigmoid 和 Conv、Concat、Add、Resize、Reshape、Transpose、Split

yolo模型中神经节点Mul与Sigmoid 和 Conv、Concat、Add、Resize、Reshape、Transpose、Split 在YOLO&#xff08;You Only Look Once&#xff09;模型中&#xff0c;具体作用和用途的解释&#xff1a;

接口自动化测试从入门到高级实战!

接口测试背景和必要性 接口测试是测试系统组件间接口&#xff08;API&#xff09;的一种测试&#xff0c;主要用于检测内部与外部系统、内部子系统之间的交互质量&#xff0c;其测试重点是检查数据交换、传递的准确性&#xff0c;控制和交互管理过程&#xff0c;以及系统间相互…

深入浅出计算机网络 day.1 概论③ 电路交换、分组交换和报文交换

人无法同时拥有青春和对青春的感受 —— 04.3.9 内容概述 01.电路交换、分组交换和报文交换 02.三种交换方式的对比 一、电路交换、分组交换和报文交换 1.电路交换 计算机之间的数据传送是突发式的&#xff0c;当使用电路交换来传送计算机数据时&#xff0c;其线路的传输效率一…

Rust教程:How to Rust-从开始之前到Hello World

本文为第0篇 专栏简介 本专栏是优质Rust技术专栏&#xff0c;推荐精通一门技术栈的蟹友&#xff0c;不建议基础的同学&#xff08;无基础学Rust也是牛人[手动捂脸]&#xff09; 感谢Rust圣经开源社区的同学&#xff0c;为后来者提供了非常优秀的Rust学习资源 本文使用&…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Image)

Image为图片组件&#xff0c;常用于在应用中显示图片。Image支持加载PixelMap、ResourceStr和DrawableDescriptor类型的数据源&#xff0c;支持png、jpg、jpeg、bmp、svg、webp和gif类型的图片格式。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&am…

【C/C++】常量指针与指针常量的深入解析与区分(什么是const int * 与 int * const ?)

目录 一、前言 二、const 的简单介绍 三、常量指针 &#x1f50d;介绍与分析 &#x1f4f0;小结与记忆口诀 四、指针常量 &#x1f50d;介绍与分析 &#x1f4f0;小结与记忆口诀 五、总结与提炼 六、共勉 一、前言 在【C/C】的编程中&#xff0c;指针与const关键字的组合…

大模型笔记:幻觉 hallucination

1 介绍 “幻觉” (Hallucination)&#xff0c;指模型生成自然流畅&#xff0c;语法正确但实际上毫无意义且包含虚假信息即事实错误的文本&#xff0c;以假乱真&#xff0c;就像人产生的幻觉一样。 举个例子就是&#xff0c;即使现在的chatgpt-4&#xff0c;你问他一些有确切…

面向切面编程(AOP)介绍(横切关注点、通知(增强)、连接切入点、切面)

1. 面向切面编程思想AOP AOP&#xff1a;Aspect Oriented Programming面向切面编程 AOP可以说是OOP&#xff08;Object Oriented Programming&#xff0c;面向对象编程&#xff09;的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构&#xff0c;用于模拟公…

Qt 实现诈金花的牌面值分析工具

诈金花是很多男人最爱的卡牌游戏 , 每当你拿到三张牌的时候, 生活重新充满了期待和鸟语花香. 那么我们如果判断手中的牌在所有可能出现的牌中占据的百分比位置呢. 这是最终效果: 这是更多的结果: 在此做些简单的说明: 炸弹(有些地方叫豹子) > 同花顺 > 同花 > 顺…

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>…

Oracle LeetCode 高频 SQL 50 题(进阶版)

https://leetcode.cn/studyplan/sql-premium-50/ 一、查询 1821. 寻找今年具有正收入的客户 selectcustomer_id from Customers where year 2021 group by customer_id having sum(revenue) > 0183. 从不订购的客户 select c.name as Customers from Customers c left j…

Windows-Wireguard-Watchdog,Wireguard服务端DDNS客户端自动重连

Windows-Wireguard-Watchdog 背景&#xff1a;Wireguard服务端是动态IP&#xff0c;Wireguard客户端配置的服务端地址是域名 Endpoint xx.xxx.com:51820&#xff0c;写的DDNS脚本检测到IP变更会自动更新域名解析。每次宽带重拨IP就会变更&#xff0c;由于客户端重连使用的不是…

VSCODE解决git合并过程中的冲突问题;error: failed to push some refs to

1&#xff1a;异常现象 推送有冲突的git修改到远端的时候&#xff0c;会有如下提示 git.exe push --progress “origin” master:master To http://gitlab.xxx.com/dujunqiu/test.git ! [rejected] master -> master (fetch first) error: failed to push some refs to ‘…

00在linux环境下搭建stm32开发环境

文章目录 前言一、环境搭建1.arm-none-eabi-gcc2.openocd 三、创建stm32标准库工程1.创建工程目录2.修改stm32_flash.ld文件3.写makefile文件4.修改core_cm3.c5.写main函数并下载到板子上 最后 前言 我在那天终于说服自己将系统换成了linux系统了&#xff0c;当换成了linux系统…