之前写过这道题的练习笔记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];