CSP 202109-3 脉冲神经网络练习笔记2 100分代码

之前写过这道题的练习笔记1:脉冲神经网络练习笔记1

笔记1里贴出的代码只有66分,原因是运行超时(有没有人发现这个字其实是粉红色的 (。・∀・)ノ゙)。

笔记1里我提出了一些当时未理解的问题

1.mod的设置?:mod=max(mod,Tuchu[S_tmp].D+1);
2.时间t_tmp的设置?:int t_tmp=i % mod;
3.I_k数组为何要清空?memset( I_k[t_tmp], 0, sizeof I_k[t_tmp]);

其实总结起来: 这都是对T时间循环的不理解。

接下来说说更改后的100分代码及我的思考:

1.使用图结构存储突触的信息

之前的代码之所以只有66分,我在笔记1中也说过,应该是因为:依次遍历不同的突触寻找入结点的相应脉冲源和神经元,耗时过长。当时我没有想到要用图结构存储突触的信息,所以在选择了for循环的方式,遍历每一个突触,效率低的惊人,超时是必然的。。。

因为前几天做了CSP 202009-3 点亮数字人生这道题(点亮数字人生题解)(夹带私货 (。・∀・)ノ゙),了解了图的知识点。(在此深刻的反思:本人解题只会暴力,很惭愧,大三了还是一点数据结构和算法都不懂,所以一道题要是要用到除了一维数组,二维数组,for循环这种基本基本又基本之外的东西的话,我是绝对不会的,目前正在学习中 ˙—˙)

回到正题:这次我将突触的信息创建了邻接表储存在图中,由于突触的信息不仅有入结点s和出结点t,还有这条突触(边)的脉冲强度w和传播延迟D,即边的权重,因此我建立了一个带权重的邻接表,实现了从66分到100分的进步。

一个带权重的邻接表的创建示例如下:

建立结构体Node,用来存放边的终点编号和权重信息:

struct Node
{   
	int v;//边的终点编号
	int w;//权重
	Node(int _v, int _w) 
	{
		v = _v;
		w = _w;
	}
};

这样,邻接表的元素类型就是Node型的,如下:

vector<Node> G[MAX_NUM];

实现加边操作:

G[1].push_back(Node(3,4));

完整的满分代码如下:

#include<bits/stdc++.h>
using namespace std;

const int MAX_NUM=2010;
const double INF=1e8;
int N,S,P,T;
double  t1;
double I_k[MAX_NUM/2][MAX_NUM/2];

struct node
{
   double v,u,a,b,c,d;
   int count_put;
}Node[MAX_NUM/2];

struct tuchu
{   
	int t;
	int D;
	double w;
	tuchu(int _t, double _w, int _D) 
	{
		t = _t;
		w = _w;
		D = _D;
	}
};
vector<tuchu> G[MAX_NUM];

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

int main()
{
    std::ios::sync_with_stdio(false);
    cin>>N>>S>>P>>T;
    cin>>t1;

    int temp_N=N;
    int N_tmp=0;
    while(temp_N>0)
    {
        int RN;
        cin>>RN;
        temp_N-=RN;
        int RN_tmp=RN;
        double v,u,a,b,c,d;
        cin>>v>>u>>a>>b>>c>>d;
        while(RN_tmp>0)
        {
            Node[N_tmp].v=v;
            Node[N_tmp].u=u;
            Node[N_tmp].a=a;
            Node[N_tmp].b=b;
            Node[N_tmp].c=c;
            Node[N_tmp].d=d;
            N_tmp++;
            RN_tmp--;
        }
    }

    int maic[MAX_NUM];
    int P_tmp=0;
    int temp_P=P;
    while(temp_P>0)
    {
        int r;
        cin>>r;
        maic[P_tmp+N]=r;
        P_tmp++;
        temp_P--;
    }

    int S_tmp=0;
    int temp_S=S;
    int mod=0;
    while(temp_S>0)
    {
        int s,t,D;
        double w;
        cin>>s>>t>>w>>D;
        G[s].push_back(tuchu(t,w,D));//存储图
        mod=max(mod,D+1);
        temp_S--;
        S_tmp++;
    }
    //-----------输入完毕-------------
    for(int i=0;i<T;i++)
    {
        int t_tmp=i % mod;
        for (int j=N;j<P+N;j++)
        {
            if(maic[j]>myrand())
            {
                for(int k=0;k<G[j].size();k++)//遍历脉冲源j的连接的神经元
                {
                    int node_get=G[j][k].t;//脉冲源j的连接的神经元
                    double w_tmp=G[j][k].w;
                    int D_tmp=G[j][k].D;
                    I_k[(t_tmp+D_tmp)%mod][node_get]+=w_tmp;
                }
            }
        }

        for (int j=0;j<N;j++)
        {
            double v_tmp=Node[j].v;
            double u_tmp=Node[j].u;
            double a_tmp=Node[j].a;
            double b_tmp=Node[j].b;
            double c_tmp=Node[j].c;
            double d_tmp=Node[j].d;
            Node[j].v=v_tmp+t1*(0.04*v_tmp*v_tmp+5*v_tmp+140-u_tmp)+I_k[t_tmp][j];
            Node[j].u=u_tmp+t1*a_tmp*(b_tmp*v_tmp-u_tmp);
            if(Node[j].v>=30)
            {
                Node[j].v=c_tmp;
                Node[j].u+=d_tmp;
                Node[j].count_put++;
                for(int k=0;k<G[j].size();k++)//遍历脉冲源j的连接的神经元
                {
                    int node_get=G[j][k].t;//脉冲源j的连接的神经元
                    double w_tmp=G[j][k].w;
                    int D_tmp=G[j][k].D;
                    I_k[(t_tmp+D_tmp)%mod][node_get]+=w_tmp;
                }
            }
        }
        memset(I_k[t_tmp],0, sizeof I_k[t_tmp]);
    }

    double max_v=-INF;
    double min_v=INF;
    int max_count=-INF;
    int min_count=INF;
    for (int i=0;i<N;i++)
    {
        max_v=max(max_v,Node[i].v);
        min_v=min(min_v,Node[i].v);
        max_count=max(max_count,Node[i].count_put);
        min_count=min(min_count,Node[i].count_put);
    }
    cout<<setiosflags(ios::fixed)<<setprecision(3)<<min_v<<" "<<max_v<<endl;
    cout<<min_count<<" "<<max_count<<endl;
    return 0;
}

满分代码提交结果:
在这里插入图片描述
2.对T时间循环的不理解

也许你看不懂为什么要设置mod,为什么时间遍历要使用当前时间t%mod,(坦白说,我就看不懂),为了搞清楚为什么,我在遍历时间的时候,直接用了当前时间t,而不是t%mod,(直接用当前时间t的话,I_k数组的大小也要改,上面的代码中设置的I_k数组大小为:I_k[MAX_NUM/2][MAX_NUM/2],但当时间遍历直接使用当前时间t时,I_k数组大小应改为为:I_k[10000][MAX_NUM/2],因为T<=1e5)于是出现了下面的情况:
在这里插入图片描述
拿不到满分的是因为T太大了,所以相应用于存储当前时刻的神经元接收到的脉冲强度的数组I_k[10000][MAX_NUM/2]也很大(可以看见空间使用了88.02MB,上面的代码空间使用只有10.57MB),为了降低I_k数组的大小,我们使用循环数组:如果传播延迟最大是D,那只要记录当前时刻往后的D时刻内的变化就行,因此使用当前时刻对D+1(即mod)取模以节约I_k数组的大小,降低复杂度。注意因为使用了循环数组,所以每一个时刻相应的神经元内部状态更新后,需要清空该时刻对应的I_k数组:memset(I_k[t_tmp],0, sizeof I_k[t_tmp])。(循环数组好神奇,本菜鸡还在领悟中 ˙—˙)

3.对代码中数组,结构体大小的解释

笔记1中,我将MAX_NUM设置为1005,还在喊冤为什么只有33分,所以今天的我来解释一下昨天的我的困惑,这次的代码我的数组,结构体的大小设置如下:

1)首先设置常量MAX_NUM=2010

const int MAX_NUM=2010;

2)I_k表示当前时刻的神经元接收到的脉冲强度的数组,第一个[]是时刻,第二个[]是神经元编号。为什么大小是MAX_NUM/2而不是MAX_NUM呢?
第一个[]表示当前时刻:由于我们的时间遍历没有用真正的当前时刻,而是用当前时刻对传播
延迟的最大值 D+1(即mod)取了模,由题知D的最大值是1e3,故第一个[]大小为MAX_NUM/2
第二个[]是神经元编号:神经元个数N的最大值是1e3,故第二个[]大小为MAX_NUM/2

double I_k[MAX_NUM/2][MAX_NUM/2];

3)Node结构体表示神经元,神经元个数N的最大值是1e3,故该结构体大小为MAX_NUM/2

Node[MAX_NUM/2];

4)G是存储突触信息的邻接表,为什么邻接表的大小又是MAX_NUM呢?
突触表示的是神经元-神经元、脉冲源-神经元的连接关系,每一条突触的入结点即可能是神经元也可能是脉冲源,神经元个数N和脉冲源个数P的最大值都是1e3,突触的入结点至多有MAX_NUM个,故邻接表大小为MAX_NUM

vector<tuchu> G[MAX_NUM];
  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值