LOFTER for ipad —— 让兴趣,更有趣

点击下载 关闭
近期编程经历分享&洛谷网站通过的第一道普及+难度题目分析
蓝天白云 2020-02-20

       大家好,今天给大家带来一篇有关我近期编程经历的日志。首先需要说明的是,我在大学本科的专业并不是计算机,而是数学。有关计算机方面的专业知识,我了解的还不是很多,所以这篇文章中的一些文字叙述可能不太专业,不太准确,也可能会出现一些错误,如果大家发现,还请多多指教。这篇日志篇幅较长,有近一万字。之所以写这么多,主要是为了在分析题目的时候(本日志的后半部分)力求讲述得通俗易懂,让即使没有编程经历的朋友也可以理解。希望大家可以在空闲的时候慢慢阅读,跟着我的思路一起做一套“思维体操”,相信你会有所收获的。

       记得我初次接触编程是在高一第一学期,当时初次了解洛谷网站(一个“富有亲和力”的在线判题系统),觉得好玩,就心血来潮注册了账号,并尝试自学网站支持的其中一种语言——Pascal。然而,坚持了没多久,我就放弃了,倒也不是因为丧失了兴趣吧,可能是因为学习变繁忙再加上自己贪玩,也可能是觉得这样漫无目的地学没有太大意义吧。接下来,在高一第二学期的信息课上,我又学到了VB。因为有之前自学Pascal的经验,我学得很轻松,不过由于课时比较少,实际上也并没有学到太多内容。再之后,进入高二、高三,学习就越来越繁忙了,所以我也几乎没有再编过程序。就这样一直到了大二,开始学习我们院系的公共基础课“高级计算机语言”时,我才开始了新一轮的编程之旅!

       “高级计算机语言”这门课当中,我们主要学习的是C++编程语言。这一回,由于有了之前学习Pascal和VB的经验,我的上手速度就更加快了,同时,可能由于年龄增长,学习能力增强的缘故,我常常将新知识和其他学科中出现的相似知识点作类比,也比较善于在网上查找一些相关资料,从而加深我对某些知识点的理解。

       在上了几次课,了解了C++的一些基本语法之后,我觉得是时候重返洛谷,做一些题目来巩固所学知识了。虽说在学校也有上机实验课,会要求编一些小程序,而且老师也曾在我们学校的判题网站上发布过两套练习题,每套包含15道小题(其中5道是直接给了代码的例题),但那些和洛谷上的题相比,大多都还是相当简单的。几乎每次实验课,我都能提前完成任务,余下一段时间自习;老师发布的第一套题,我甚至只用了一个晚上大约2个小时的时间就全部做完了;在学校的判题网站上提交代码时,我也从来都是一遍通过。这些不错的成就都使我更加自信。重回洛谷后,我继续挑战新手村的题目,最终在去年(2019年)12月26日成功通关新手村。

       在展示我编的部分程序之前,有必要先说一下我编C++程序时的一些习惯。

 

1、主函数尽可能短小,主要通过在主函数中调用其他函数来完成所需要求

       这条是从课上学来的。老师一直提倡我们要学会把任务分担到一个个函数中去完成,最后在主函数中去调用这些函数,让主函数起到一个支配全局的效果。我觉得还是很有道理的。这样做的话,在写程序的时候头脑就会比较清晰,能够很明确地知道我现在写的这段代码需要完成什么功能。如果是多人合作项目,把任务分解成一个个小模块,让每个参与者都写几个函数,最后拼接到一起,通过主函数去调用那些函数,也是很不错的办法。

 

2、语句尽量简单直白,不刻意减少代码行数

       这条可能有的朋友会不太同意。或许是我现在编的程序还算比较简单,暂时还没有让我感觉到这样做的弊端吧。我个人不是特别在意一些可以压缩代码行数但对运行速度影响不是很大的小技巧,毕竟程序也不可能因为书写紧凑而执行效率高,写得简单直白一点,我觉得反而是写起来轻松,看起来也轻松。

 

3、自己编程序时很少写注释

       这条可以算是一个坏习惯吧。我在解决问题的时候,比较注重的是思路。一旦有了大体思路,将其转化成代码实现应该只需要花上一点时间,再加上一点细节的考虑。如果你要问我在洛谷上做题主要的收获是什么,我的回答会是:利用计算机解决实际问题的思路。至于上传代码参加评测,只是检测这样的思路对不对、好不好罢了。注释的作用,应该就是告诉别人自己的思路是怎样的。所以如果有别人想要看我的代码,我一般是会写上注释的,但如果只是自己写代码,我一般就会偷懒了。这在目前题目不太难的时候可能还没什么问题,但如果以后要写比较大的程序,写一点注释可以让自己不忘掉自己的思路,同时也便于以后再查看。当然,如果能和这篇日志的后半部分一样,把自己整个的思考过程记录下来,应该是更好的选择。

       接下来我会展示几个近期在学校编的比较有代表性的C++程序。部分程序的注释是后来加上的。

1、随机数的算术与几何平均值计算(第二周实验内容之一)


2、谁是小偷(第三周实验内容之一)

       题目背景(摘自实验内容文档):警察抓住了A、B、C、D四名盗窃嫌疑犯,其中只有一人是小偷。在审问时,A说:“我不是小偷”;B说:“C是小偷”;C说:“小偷肯定是D”;D说:“C在冤枉好人”。现在知道四人中有三人说的是真话,一人说的是假话。请问到底谁是小偷?

 

3、奇数阶幻方的生成(第四周实验内容之一)

       奇数阶幻方(n为奇数)的生成方法(摘自实验内容文档):将1列于第一行正中,依次将自然数排在其前一个数的右上方(按环绕边界处理);遇到n的倍数时,将下一个自然数排在前一个数(n的倍数)的下方。

 

4、高精度乘法计算(第二套练习题最后一题)

       有趣的是,在做完这题之后,我想起了四年多前在洛谷挑战失败的一题:P1480 A/B Problem。当时的我还天真地以为这题会和A+B Problem一样简单,结果自然是一片WA(Wrong Answer)。后来才知道,这题其实也是一个高精度的计算,只不过这次换成了除法(C++中每一种变量的数据类型都有其所能表示的范围,如果运算过程中有哪一步超出这个范围,也就是我们常说的数据溢出,运算的结果将不可预知。如果我们想要利用计算机对可能超出这个范围的数据进行运算,就要采取一些特殊的方法)。写这两题的代码时,我采用了相似的格式。最终,我成功解决了这道四年前的“难题”!不过,由于篇幅有限,洛谷题目的代码就不在这里给大家展示了,有兴趣的朋友可以搜索一下我在洛谷的提交记录,应该可以找到所有的代码。

       编了这么多的程序以后,我感觉自己的能力还是有不小的提升的,再加上寒假的视频计划已经基本完成,我渐渐有了一些空闲的时间。于是,我决定,在洛谷网站上尝试挑战一道稍难一些的题目。我最初的想法是挑战一道比我原本做过的最高难度的题(普及-)高一个难度档次的题,然而,我忘记了比“普及-”高一个难度档次的应该是“普及/提高-”,而直接跨越两个难度档次做了一道“普及+/提高”的题目……事实上,我在通过了这道题之后才意识到我犯了这样一个“错误”,不过这或许也是一件好事吧。如果在做之前我就知道这题的难度比原先我做过的最高难度高两个档次的话,或许我就没有信心将其拿下了。

       我做的这道题是P1004 方格取数。我于2月13日晚上23时左右初次尝试这道题,几经周折,多次更换思路,反复进行优化,终于在2月16日中午成功通过评测。这其中的曲折经历,我觉得还是很值得写文章记录一下的,既是帮自己做个总结,又或许可以给大家提供一些不同的思路。

       题目链接:https://www.luogu.com.cn/problem/P1004

       有兴趣的朋友可以先思考一下,或者动手做一下,再继续看下面的内容。(当然,如果你是“大犇”,请随意)


       下面我会尽可能地复现我解这道题时复杂的心路历程,我个人感觉还是很有意思的,大家可以像看故事一样来看。

       首先我来复述一下题目。

       P1004 方格取数:

       有一个N*N的方格图,其中部分格子中会填入一个正整数,其余格子中则都为0。你从左上角出发,每一步只能向下或者向右走,最终走到右下角,路上经过的数字会被取走,取走后该数字变为0。现在给定(输入)N,以及方格中各个数的坐标,问(输出):从起点到终点重复走两次,可以取得的数字之和最大是多少?输入时保证N≤9。

       来看一个题目中给的样例:

       通过观察应该不难发现,最优的两条路径如下(不唯一),可取得的最大的数字之和是67。

       图中红色和绿色分别代表两条路径,黄色代表两条路径的重合点。需要注意的是,如果严格按照题目中的叙述来理解的话,这两条路径应该是有先后的。也就是说,先走完一条路,然后这条路上的数字全变为0(都被取走了),然后再走另一条路。不过,很容易看出,当你选定好两条路径后,先走哪条的结果都是一样的。

       初次看到这道题时,我察觉到了这道题目疑似的两个关键点,一是“最优”,二是“两次”。

       关于“最优”,我想到的是,当我们处在方格图中的某个位置时,我们应该如何判断往下走更优还是往右走更优呢(暂时考虑只走一次)?我最初单纯的想法是:如果往下走,你就永远失去原本位置的那一行的所有数字了;如果往右走,你就永远失去原本位置的那一列的所有数字了。所以,通过比较所处位置右方和下方的数字,应该就可以判断往哪走更好。不过这个想法很快被我否决了,因为事实远远没有那么简单,比如下面两张图:

       你站在起始点的位置,如果往下走,你就永远失去了右上角的5;如果往右走,你就永远失去了第一列的4个1。所以为了失去的数字之和更少,看起来应该往右走才是对的。在第一张图中,确实如此,但如果把右下角的16个0换成别的数字,情况就很有可能不一样了。就如第二张图,如果在起始点往下走,最多可以得到7,但如果往右走,最多也还是只能得到5。为什么这样的思路会错呢?我后来思考了一下,应该是因为这样的思路仅仅考虑了数字之和的大小,却没有考虑数字离现在所处位置的远近,从而导致判断失误。但是如何把远近也结合进来判断,我也实在是想不出好办法了,理论上应该是有解的,但应该和整个方格图中所有的数字都有关联,可能会非常复杂。最终,我放弃了这种“判断最优路径”的思路,决定利用计算机速度上的优势,尝试用一种直接粗暴的方式来解决问题——穷举!(好像专业叫法是穷举搜索法)事实上,我后来了解到,这应该是一个动态规划的问题,如果了解相关的算法知识(下面会简单提到),确实是可以做到每一步都走到最优,更高效地解决问题的,然而,由于我当时并不了解这样的算法,只好采用较低效的方式了……

       穷举的思路很简单:把从起点到终点所有可能的路径都走一遍,看看走哪条路最终得到的数字之和最大就行了。然而,“两次”这个题目要求使我产生了一个很大的疑惑。要做到两次都走最优路线并不难,只要第一次先穷举所有路线,把最优的路线记下来,然后把这条路上经过的所有数字都清零,这样就可以得到一个更新后的方格图,然后再针对这个方格图把最优路线穷举出来就可以了。但是,这两次最优路线取得的数字之和一定就是整体上最优的吗?大家可以先思考一下,如果觉得是,试给出理由;如果觉得不是,请给出反例。答案会在下文揭晓。

       一时半会我也没有想出这个问题的答案,于是我决定先按这个思路试着做一下,如果出现问题再修改。如何让计算机能够“走遍”所有的“路线”呢,我很快想到应该将走路线的过程数字化。对于一个N*N的方格图,从左上角走到右下角,如果只能向下或者向右走的话,不论走什么样的路径到终点,都必然要向下和向右各走N-1次才行,如果用1代表向下走,0代表向右走,这样,一串长度为2N-2,其中0,1各占一半的代码,就对应着一条路径。只要让计算机把所有这样的代码生成一遍,就算是“走遍”了所有的“路线”!但怎么样生成所有的这些代码呢?这又让我犯难了一会,最终决定采用如下递归的方式来实现。

       这个Path函数中主要用于控制路径代码生成的参数是X和Y(其他参数可暂时忽略不看),分别代表横向和纵向剩余可走的空间。一开始它们的值都是N,然后开始生成路径代码,如果是0(向右走),就调用Path(X-1,Y);如果是1(向下走),就调用Path(X,Y-1);如果某一次调用时发现X变为了1(代表向右走到了尽头),但Y还大于1,那接下来Y-1个代码都是1(只能向下走);如果发现Y变为了1也是同样的道理。如果X和Y都成了1,就说明走完了,这时候可以把路径上的数加起来,和之前的最优成绩比较,如果更好,就更新最优成绩和最优路径;如果不好,就回退到上一步,改换其他路径。

       需要说明的是,不同层次调用的Path函数中的X和Y是相互独立的,所以我上面说X(或Y)“变为”了1其实是不严谨的,因为实际上前后是两个不同的局部自动变量,真正的过程应该是通过Path(X-1,Y),将前一次的X的值减一后,“传递”给了后一次的X的初始状态,然后发现这个初始状态是1。所以,虽然它们都叫做X,实际上已经是两个世界的了。另外,每一步的代码也不是同时生成0和1两种可能性。如果按照我的代码写法,每次二选一时永远都是先生成0,然后继续往后运行,直到走到终点,再回到最靠后的一个分歧点,改成生成1,然后继续往后运行……也就是说是“深度”优先的穷举搜索——把一条路先走到底,再倒回去走其他路。例如N=3时,生成代码的顺序应该是0011,0101,0110,1001,1010,1100。总之,上面的那段叙述仅仅是为了便于理解,实际的运行过程与之稍有些偏差,但不会影响准确性。

       我不知道能不能不用递归来实现这样的效果,因为我知道递归其实还是挺耗时间的。此题的时间限制是1秒,我很担心当N比较大时会超出时间限制(TLE)。幸好,题目中明确说了N最大不会超过9,这应该还不算特别大。就算N=9,从数学上不难想到,总共可行的路径应该有C(16,8)=12870条(从起点走到终点要走16步,其中向下(向右)占8步,所以就是组合数C(16,8))。按照我现在的算法,走两遍无非就是再把数量乘个2(因为第一遍找出最优路径后,走第二遍仍然是和第一遍同样类型同样规模的问题,只是方格图上的数有所调整),这个数字对计算机来说还是小意思,于是我就编出了上面展示的那个程序,然后提交,结果如下:

       这样的结果也算在我的意料之中吧,因为我很清楚,我还是没有仔细考虑“两次”这个问题,就算我侥幸通过了评测,这个问题也会成为我心中的一个疙瘩。现在,我不得不直面这个问题了。由于当时内心焦急,想快点通过这题,我直接选择下载了测试点#3想看看反例究竟长什么样,其实要是能自己想出反例的话就更好了。

       下面我给出一个上面提到的“两次”问题的反例,为了不透露测试点信息,这个反例和#3不会完全相同,但类型差不多。

       对于这样的方格图,如果按照走两次最优路线的方法,第一次走时,必然把两个10都拿上是最好的,然而,一旦你拿上两个10,就意味着你不可能拿到角上的两个1,而第二次走时也只能拿到其中的一个1。但是,如果换一种走法,显然走两次是可以拿全所有的数字的。虽然这种走法在走第一次时达不到最优,但两次后的最终成绩却确确实实可以达到其他任何走法都达不到的高度。

       仅仅这样的一个反例,就足以将原来的方法判了“死刑”。这件事情也告诉我们:局部最优的叠加不一定能达到全局最优。我后来了解到,我的这一方法有点类似于“贪心”算法,也就是说通过一系列局部的最优解最终来达到全局最优。在某些时候,贪心算法确实是可靠的,但是在现在这种情况,因为第一次的“贪心”会对之后的情况造成影响(具有后效性,即方格图上的数字会被改动),导致贪心算法失效。

       怎么来解决这个问题呢?我很快想到了一种在原来的基础上改进的办法:把两次的路径拼接起来,穷举所有可能的情况。也就是说,把路径的代码加长一倍,前面一半代表第一次走的代码,后面一半代表第二次走的代码,这样相当于把两次行走合并到一起,对应一种整体的情况。通过这样的代码可以直接算出两次行走的总成绩,在其中找出最好的,必然就是所要的答案了。

       这样的想法虽然简单直接,但是却不得不面对一个问题:超时。路径代码的增长导致穷举的数量以近乎指数级的速度增长。原先的方法,最多需要穷举的数量只有C(16,8)*2=25740种,而现在的方法,则有多达C(16,8)^2=165636900种(因为代码前后两部分0,1的数量都不是独立的,所以比2^32要小)。虽然从上次的提交记录可以看出原先方法只花了几毫秒,但按照比例转化成新方法(当然实际的比例不会精确等于两个数字之比,因为运行过程中还有很多其他因素,但不会相差太多),也肯定远远超过1秒了。不过,虽然知道会超时,我还是尝试写了一下,并提交参与了评测,毕竟这也是目前我唯一能想到的办法了……

       最终的结果当然也在意料之中。唯一值得欣慰的是,原先错误的第三个测试点终于正确了。但对于N=8和N=9的情况,必然是会超时的。(第三个测试点是N=7)

       现在,我们基本可以保证答案是正确的,但是怎么优化来让程序运行更快呢?那肯定是要想办法让计算机少做一些“无用功”。比如,两次走的路径完全相同,显然就不是一个好的方案。所以,我们可以在穷举搜索的过程中加入一些判断,一旦走到哪一步时发现这样走相比其他走法是较劣的方案,就立刻终止,回退到上一步去走其他的路。这种做法似乎有个专业的名称:“剪枝”。我觉得还是很形象的。

       那么,什么样的走法是较劣的呢?所谓较劣,就是不管方格图中的数字分布是怎么样的,一定可以找到其他的走法,不比这种走法差。通过分析题目可以发现,我们走完两次后得到的成绩,实际上也就是两次的路径所覆盖的格子中的数字之和。所以,我们自然可以得出这样一个基本事实:如果走法一覆盖的格子包含于走法二覆盖的格子,那么走法一相对于走法二就是较劣的(注意是包含于而不是少于)。例如下面两张图,不管方格图中的数字是什么,上面的走法总是劣于下面的。

       利用这个事实,可以推出如下几条结论:

       结论1:两条路径的第一步走相同方向一定是较劣的。

       证明:不妨设两条路径的第一步都是向右。接下来有两种情况:

       1、两条路径在同一个格子拐弯向下走

       2、两条路径在不同的格子拐弯向下走

       不管是哪一种情况,只要改换其中一条路的走法,让其先向下走一步,再向右走到原本拐弯向下的位置,显然都可以达到更优的效果(因为覆盖的格子在包含原先的基础上又增加了新的)。

       利用结论1,我们就可以直接规定让第一条路先向右走一步,第二条路先向下走一步,相当于直接确定了路径代码中的两个原本随机的数字。可别小看这一点小小的优化,它可以减少一大半的工作量!

       我们还可以从结论1中得出一个推论:两条路径的最后一步走相同方向也一定是较劣的。原因很简单:这两条路径也可以想象成是从终点走向起点的。虽然这个推论不方便用来帮助我们优化(因为任意一条路走到方格图的右边界或下边界时,就可以“自动”走到终点,无需产生0,1之间的随机数),但它可以用来帮助我们证明结论2。

 

       结论2:两条路径有重合点时(除去起点和终点)一定是较劣的。

       证明:留作习题。(滑稽)

       自己打出这几个字的时候感觉好爽啊!哈哈,开个玩笑!

       提示:第一个重合点在右边界或下边界时,由推论即证;第一个重合点在内部区域时,考虑怎么样改换走法会更好。这里请允许我稍微偷一下懒吧。

 

       结论3:如果某种走法其中的一条路径的第一步是向右(下),最后一步也是向右(下),这种走法一定是较劣的。

       证明:设第一条路径的第一步是右,最后一步也是右。因为路径一的第一步是右,由结论1,路径二的第一步应该向下,但是之后无论路径二怎么走,都必然会和路径一在中途出现重合点,由结论2可知结论3成立。

       如下图,另一条路不论怎么走,都一定会和绿线在某处出现重合。

       从路径代码上可以更严格地说明这一点。我们同时读两条路径的代码,每往后读一位,就把这一位包括前面所有位的数字求和,如果和相等,就代表两条路在这个位置重合了(因为和相等说明包含相同数量的0和1)。如果路径一的第一步是右,最后一步也是右,意味着路径代码的第一位和最后一位(第2N-2位)都是0,所以其前2N-1位之中就包含了N-1个1了。而路径二的第一步是下,所以路径代码的第一位是1,这比路径一的代码的第一位要大,所以,接下来,如果想让两条路不重合,路径二必须一直保持其代码前面任意位的数字之和大于路径一的(因为一旦出现小的情况,在大和小中间就一定有相等的情况,而相等就意味着重合),一直到前2N-1位,为了比路径一大,和至少要达到N,但这是不可能的,因为N阶的方格图不可能允许你向下走N次,也就是取N个1,于是得出矛盾。

       利用结论2,在走路径二时,我们可以每走一步,就判断一下有没有和路径一出现重合,如果重合,就直接回退;利用结论3,在走路径一时,只要走到了下边界上面一行的位置,就可以直接让它向右走到尽头,再向下一步走到终点,提前结束路径的选择。总之,这些结论可以帮助我们剔除劣策略,从而显著提高程序的运行的速度。

       其实,上面三条结论不是我一口气想出来的。真实情况是,我一边想一边优化,每次优化了一点之后都会试着提交一下碰碰运气,但是,每一次都会在测试点#2超时(估计这个点是N=9的情况)。最终,当我理清思路,完整总结出了三条结论后,才总算勉强通过了这个测试点,同时通过了这整道题的评测!

       可以看出,在测试点#2上我的程序花了926毫秒,恰好少于题目要求的1秒(1000毫秒),算是低空飞过吧。不过我也已经对自己感到很满意了,毕竟反复多次的优化终于有了成效。

       不过,在查看评测记录的时,看到自己的用时好像真的就是所有通过的用户之中最长的那个的时候,我还是深深地感受到了自己和别人的差距。在看别人的题解的时候,我了解到,这道题目比较实用的思路应该是使用“动态规划”算法。暂时考虑只走一次的情况,其大概思路应该是这样的:

       因为只有从终点上面一格走到终点和从终点左面一格走到终点两种情况,所以要想知道走到终点时的最好成绩是什么,只要知道走到其上面一格和走到其左面一格的最好成绩就可以了,取其中最大者,加上终点那个格子本身的数就是走到终点最好的成绩;

       那怎么知道走到那两格的最好成绩呢?只要知道走到那两格的上面一格和左面一格的最好成绩就可以了;

       一般地,走到任意一格的最好成绩,就是走到其上面一格的最好成绩和走到其左面一格的最好成绩之中的最大者,加上这格本身的数。这应该是这一方法的核心所在。

       这样可以一直倒推到起点。也就是说,只要知道了走到起点的最好情况是什么,就可以一路推得走到终点的最好情况是什么。走到起点的最好情况我们当然是知道的,就是起点那格本身的数嘛!拿下图左边的那个3阶的方格图为例,只需要找个空地方画个一样大的方格图(参考右边),在其中填入走到那格时最多可以拿到的数字(注意应该从左上角开始逐渐向右下角填,填某个数时只需看其左方、上方和自身),当你最终填到右下角时,那个格子中的数字就是最终答案了!

       当然,因为“走两次”的问题,如果连续使用两次动态规划,必然也会像我的第一次提交一样,在第三个测试点出错。但是,同样地,如果把先后的两次行进看成是同时的话,还是可以解决这个问题的。只是,原本从一步到下一步只有两种可能:向下或向右,现在则变为四种可能:(下,下)、(下,右)、(右,下)、(右,右)。所以,其核心过程应该改为:路径一走到A格,路径二走到B格这一状态的最好成绩,是一步之前的四个可能的状态之中最好的那个,加上A格和B格中的数;特别地,当A和B是同一格时,为了避免重复拿同一个数字,应该再减去一次这个格子中的数。

       学习了这个思路以后,我很快利用这个思路新写了一个程序,并提交评测。

       可以看出,这样的算法确实比我原先的算法要快很多。这也完全可以理解——这个算法中没有之前的递归那样多次的函数调用,仅仅是四层循环而已。从现实角度来看,在最麻烦的N=9的情况下,这种方法只需要将一个9*9*9*9的四维“表格”填完即可,因而只需9^4=6561步,这可比原先的穷举165636900种路径(尽管后来删减了不少)容易太多太多了!不过,尽管这种算法可以瞬间将我原来的算法“秒杀”,我依然认为我为了优化原先的算法所花的时间是值得的。

       这篇日志到这里也就接近尾声了。其实,我在写这篇日志之前也没有想到自己能就这一道题目写出这么多内容来。不可否认,从这道题中,我锻炼了自己的思维,巩固了原先所学的知识,还收获了许多有用的解决问题的新方法,这些都是令我感到十分满意的。当然,我希望这篇日志也能给大家带来一定的启发。感谢大家能一直看到这里!接下来我还会继续深入学习各项课程,不论是和专业相关的还是我自己感兴趣的,我都会认真对待。也祝愿各位同学能够学有所成,共勉!


推荐文章
评论(3)
联系我们|招贤纳士|移动客户端|风格模板|官方博客|侵权投诉 Reporting Infringements|未成年人有害信息举报 0571-89852053|涉企举报专区
网易公司版权所有 ©1997-2024  浙公网安备 33010802010186号 浙ICP备16011220号-11 增值电信业务经营许可证:浙B2-20160599
网络文化经营许可证: 浙网文[2022]1208-054号 自营经营者信息 工业和信息化部备案管理系统网站 12318全国文化市场举报网站
网信算备330108093980202220015号 网信算备330108093980204230011号
分享到
转载我的主页