QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 4446|回复: 1
打印 上一主题 下一主题

❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2021-7-8 15:06 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    4 f. e# i$ Z* x5 l1 u: \. E0 `
    ❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)) n0 L- Q5 V5 L4 C: Q$ T# k0 ^* R& Q

    ) e, d6 ?: q* c* q$ Y. {前言" ~% z2 ]% h3 L/ Q0 Y4 I
      所谓活到老,学到老,虽然我感觉自己已经学了很多算法了,但是昨天熬夜整理完以后发现,自己还是个弟弟,实在忍不住了,打算把 算法学习路线 发出来,我把整个算法学习的阶段总结成了五个步骤,分别为: 基础语法学习(重要)、语法配套练习、数据结构、算法入门、算法进阶。本文梳理了这五个大项的思维导图,在下文会有详细介绍。. d: |/ Q  O% R$ K' m
      希望各位能够找到自己的定位,通过自己的努力在算法这条路上越走越远。  z8 p2 Z) y- E2 Z# e
      刚开始切勿心浮气躁,千万不要给自己立 flag,说一定要把这么多东西都学会。就算你的精力旺盛,日夜操劳,时间也是有限的。所以,首先是明确我们要做什么,然后制定好一个合理的 目标 ,再一点一点将要学习的内容逐步付诸实践才是最重要的。
    8 m3 A4 l' M5 C4 S! c2 v  每日一篇C语言打卡,目前更新到:光天化日学C语言(20)- 赋值运算符与赋值表达式 | 让代码变得更加简介(建议收藏)。
    / |$ x% s" ^( }1 ?( g7 e  g- o) W/ ?( e7 c9 x+ `4 s

    ( c$ R9 `& R6 ~# e6 H% s/ T9 Z& r3 h: U$ m- K7 R7 r

    * ]: ~9 M4 l4 C
    8 K2 F. e2 F& O7 U* h
    . j/ b' |/ b7 K+ |0 i' v
    6 E1 |. r9 r) a4 @. L  S3 v* P1 }
    5 |: O5 B# c9 k
    图片较大,文章中有拆解,需要原图可以留言找我要哈( I( |3 K% f! }
    1、基础语法学习. A5 v; m  O4 Z* d, Y9 h7 t/ L0 v7 N) K
    算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。
    4 V, a# t" [7 f* T因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。
    - X' [3 t( h+ ?/ k3 X; g. }# @( y4 `/ E# U

    / T! T1 y1 k. a1 f( W4 K
    % U, {$ _( c5 F; p7 V

    7 D6 U: A: ^5 T  T1)HelloWorld+ E: E: e  l1 j2 r: u
    无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。
    7 t! P; _& u" z5 f% g, V2)让自己产生兴趣3 D. r9 V9 x, [' N9 w
    所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。% z4 @9 ^, Z" J( C" _
    刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。+ J- X: Q. ?4 h0 ^
    3)目录是精髓) j5 d, D6 U! d% @
    然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:5 ~+ }' s- x* R8 y
    1、第一个C语言程序
    / W. k7 z) n8 c1 f1 M5 v2、搭建本地环境$ T6 S' e* A8 c* N3 g- X
    3、变量
    $ a/ N, g" v3 V. q2 x4、标准输出" S( s2 v( Y9 Y) W6 ]+ B+ w
    5、标准输入1 P. t4 }* J) ~0 _+ T* b
    6、进制转换入门# c, g8 g, |, r4 L* }5 @; c: y
    7、ASCII字符
    $ z6 O7 X5 N6 [2 J: f- o1 q8、常量
    5 q3 {2 H' R5 `8 c: a7 C/ x4 S# c
    9 x8 F. d% V. ]5 ~7 q" h

      B2 |  W" d/ G2 f/ x, J7 t& U, ]4 A如果你觉得这些名词中有 3 / 4 以上是没有什么概念的。那么,可能需要补齐一些数学、计算机方面的基础知识。反之,我们就可以继续下一步了。
    3 N: ?+ {: v/ N2 v( Z9 Y4)习惯思考并爱上它
    6 n, ]7 D0 f$ m! c只要对一件事情养成习惯以后,你就会发现,再难的事情,都只是一点一点积累的过程。重要的是,每天学习的过程一定要吃透,养成主动思考的好习惯。因为,越到后面肯定是越难的,如果前期不养成习惯,后面很可能心有余而力不足。
    2 k* r" @$ @) ]8 [5 w就像刷题,一旦不会做就去找解题报告,最后就养成了看解题报告才会做题的习惯。当然这也是一种习惯,只不过不是一种好习惯罢了。
    # K8 b, L- s& d& ?( q; a5)实践是检验真理的唯一标准, u/ z# q: n+ V+ }; e
    光看教程肯定是不行的,写代码肯定还是要动手的,因为有些语法你看一遍,必定忘记。但是写了几遍,永世难忘。这或许就是写代码的魅力所在吧。
    # q/ b' C$ V' c& Z% m所以,记得多写代码实践哟 (^U^)ノ~YO$ R/ `/ X% H# G5 b% W0 t$ I
    6)坚持其实并没有那么难
      f* }2 ?- X/ D" m6 }" w7 C+ Y每天把教程上的内容,自己在键盘上敲一遍,坚持一天,两天,三天。你会发现,第四天就变成了习惯。所以坚持就是今天做了这件事情,明天继续做。
    * E0 A. g  Y9 d* }7)适当给予正反馈
    , j; Y- M0 x( b5 `然而,就算再有趣的教程,看多了都会乏味,这是人性决定的,你我都逃不了。能够让你坚持下去的只有你自己,这时候,适当给予自己一些正反馈就显得尤为重要。比如,可以用一张表格将自己的学习计划记录下来,然后每天都去分析一下自己的数据。
    / E) u# E" D& E6 i% S当然,你也可以和我一样,创建一个博客,然后每天更新博文,就算没有内容,也坚持日更,久而久之,你会发现,下笔如有神,键盘任我行!更新的内容,可以是自己的学习笔记,心路历程 等等。
    & B- w/ u2 F% l- C# ^看着每天的粉丝量呈指数级增长,这是全网对你的认可,应该没有什么会是比这个更好的正反馈了。9 G5 u' c+ Y# I3 m& N. m
    8)学习需要有仪式感% ~* W" @6 F  q5 L% Q! l5 ~; h
    那么,至此,不知道屏幕前的你感想如何,反正正在打字的我已经激情澎湃了。已经全然忘记这一章是要讲C语言基础的了!+ D  d  m/ M* v9 B: Y: ~) B
    介于篇幅,我会把C语言基础的内容,放在这个专栏 《光天化日学C语言》 里面去讲,一天更新一篇,对啊,既然说了要坚持,要养成习惯,我当然也要做到啦~如果你学到了哪一章,可以在评论区评论 “打卡” ,也算是一种全网见证嘛!
    3 a* j/ U6 ^- `3 Y' {我也很希望大家的学习速度能够超越我的更新速度。; {$ \( m: j/ H
    2、语法配套练习3 J; l: H# v6 ]6 d& R
    学习的过程中,做题当然也是免不了的,还是应征那句话:实践是检验真理的唯一标准。6 x/ T2 |6 A9 T  h# q
    而这里的题库,是我花了大量时间,搜罗了网上各大C语言教程里的例题,总结出来的思维导图,可以先大致看一眼:5 a3 G) z) L$ q$ E- j+ e" h% B

    $ q7 p! f- Z. p' B( o4 Z
    / {0 S% G( p3 C: s
    6 T4 O$ D* o9 \8 ^, W  W2 Y

    ) R# H% }. |; j; Q+ F5 n从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。6 b) a0 I6 p$ J: q
    这里可以列举几个例子:
    $ S% i# V; A8 G% S& l9 w  U1、例题1:交换变量的值* P; M( {) V" H" ~% r
    一、题目描述. K4 r+ i1 f: ?" ^8 D
      循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。
    ! @- g# O5 V* B3 Z
    + c: h8 v4 D! \( |

    + m, c, q) t% p2 V2 t! L/ r
    # Q, O" o0 z3 I3 d

    0 @6 t: g& \& f& E1 i2 ?2 i二、解题思路
    . E: w* P) M  J1 q4 N" o难度:🔴⚪⚪⚪⚪
    6 N3 Z  ~' A, y( O! ], p# p" ]# z5 S6 a: H2 A2 T, h, u( ~
    $ u- F; k( x3 n! _
    这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。
    8 m1 z- u. j5 k- _. Ba, b = b, a
    8 X: D2 b7 w" X* f- ~9 k1
    * X! Z/ x$ b- I在C语言里,这个语法是错误的。
    1 G( S: Y5 m; Y! F1 l我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?9 `: x2 [" b! Z2 Z" {
    当然是再找来一个临时杯子:0 g+ F/ a- n( \4 B/ ~* y/ `% O
      1)先把 a aa 杯子的水倒进这个临时的杯子里;9 D; q8 L! Q! M3 y* K
      2)再把 b bb 杯子的水倒进 a aa 杯子里;( Y( q0 x7 m  B6 e2 Y2 ^5 l8 D" |8 O
      3)最后把临时杯子里的水倒进 b bb 杯子;
    ! a$ _; `8 {  e$ K
    8 e6 l8 x& ^& w* ~" s! Y
    & s' W5 j, i0 ~( x4 ?
    这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。
    ( G" f! n# @9 r4 u& Y0 O( G" [) W3 h1 ?$ L# V5 ~+ }! j& ?

    2 B1 g7 r0 k0 c( G8 X9 [三、代码详解, Y# H( b* I0 A- O$ u. U- T
    1、正确解法1:引入临时变量
    9 J. c7 j  |/ R, X#include <stdio.h>
      n7 ]/ b$ s& Uint main() {
    , w6 g/ H% {- I7 `    int a, b, tmp;
    0 I' M2 A( B" L& `5 t7 m4 Z/ U, d1 W        while (scanf("%d %d", &a, &b) != EOF) {
    7 `) c6 d: P! a! q            tmp = a;   // (1)% Q5 j" n3 R: N3 w. H' }/ @
                a = b;     // (2)
    $ J! n% g/ Q3 Q# I5 N3 i8 j# F            b = tmp;   // (3)3 O+ T! S- o; H. m' ^
                printf("%d %d\n", a, b);  P" y$ m8 B  g2 U$ P+ i
            }# k  e" d# \& e" s, Z/ }+ ~
            return 0;
    + Z. M" d# k- j; f! c0 C0 v' ~}
    ! y% o+ v" x* P1! a* F! F4 R, E6 D7 f; ?  [9 u) g
    2
    % w: O$ i4 B; f* f5 }- g6 V* M/ Y8 X8 w% r/ y3
    3 M4 M' b8 v& Z8 e4
    ! M& _: w3 n* Y# \. p# T52 ]* X7 E$ v- K; p" q" |8 |  Y
    6
    ; A. Y. z' S4 b' N) V/ Z- s! s8 \/ B7
    6 R' V8 n8 ?7 @! E! A8- o& a* v9 ?! y3 ]0 N8 _
    9$ o. |! y/ o* `5 k0 |  b
    10
    0 y4 i1 o  P! ~& r" E) H11
    ) z# E% d; `# X" i+ R( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;
    3 n1 ]* g$ d3 u) I( h0 i( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;4 W& }) a* f1 U4 K- R8 v
    ( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;
    / `5 B* t  D$ |% N这三步,就实现了变量 a aa 和 b bb 的交换。
    6 `0 N4 u7 c8 Q8 Y2、正确解法2:引入算术运算
    $ X# l: ]1 q& b#include <stdio.h>
    0 e+ C2 [2 w' E. Cint main() {
    8 ?1 w. g# m& }    int a, b;, m7 T# R! J6 \( o0 f% Q7 L1 y
            while (scanf("%d %d", &a, &b) != EOF) {
    9 k5 M( ^! n4 V8 m- R0 Q7 _            a = a + b;   // (1)  k/ T, h' }' f0 M, U+ _
                b = a - b;   // (2)
    ' f4 y! z/ O  j$ p: \2 h% S" b            a = a - b;   // (3)
    5 ?9 W7 S  a. j1 |1 T7 q            printf("%d %d\n", a, b);8 r; k! C& n6 _5 y- B( t! D! R
            }1 x5 }- w' w7 w+ P8 w  y
            return 0;
    7 [) a; p, {' p& ]9 W}
    5 i$ m  S. W8 E% o% f+ H: L1' o7 W) L8 m2 d& k( f! a0 z0 t
    2
      u: ?% U3 C* d. v8 M3 \0 Q4 m3
    . I0 e( k" Y2 S1 j, K* U. Q44 `7 T. z, p8 K! F* M3 P
    5- @1 J* e- t4 X; t& h% o
    6
    ! N; p0 a/ j" p  N* W! P72 j/ B$ x: F3 a8 v, x3 b
    8
    5 \& Q4 p  m) L9
    9 r3 u3 k9 x4 J108 ]( B7 I% m- i# Z& \1 a
    117 l: d0 d/ i* Q7 }7 k  p
    ( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;
    - e7 v6 T# {. {4 [( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;
    # f# |6 n& Q2 D9 g2 z" q( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;
    + Y- I2 Y* K- Q从而实现了变量a和b的交换。
    ( h, ^( z' O! ~3 N6 ?3 O3、正确解法3:引入异或运算
    5 D, M6 P& ]% g8 O7 d$ k- i首先,介绍一下C语言中的^符号,代表的是异或。
    4 c% y6 x6 ~3 t2 m+ W二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:9 P* d% O. R* ]0 r& W/ n6 D" z
    左操作数        右操作数        异或结果8 D/ d0 `( E7 {' D% r$ u
    0        0        0
    ; D) J( Z/ p; j- W2 e) n1 P5 g9 ?# _1        1        0: M) C& {7 e6 b1 F- e
    0        1        1
    ! ^/ ]$ u. [+ E" \' }- i1        0        14 _/ \# ]1 _! v$ V
    也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。
    + ?4 Y, ^9 z: }: k# n, O* a" K+ u这样就有了三个比较清晰的性质:
    ; G& \, b( X' G1 G( [% ]' `1)两个相同的十进制数异或的结果一定位零。7 O4 h# ]; n3 z7 c0 O( Y- Q( w
    2)任何一个数和 0 的异或结果一定是它本身。; A! q" s( q( B! ^
    3)异或运算满足结合律和交换律。
    6 p9 z( H5 v) [% p1 ]% f#include <stdio.h>
    & ?3 ~. g# R2 _  W' Hint main() {* A* F2 X8 F: E# I) ^4 ?
        int a, b;
    1 ]) K5 a$ c" J6 x2 p        while (scanf("%d %d", &a, &b) != EOF) {- f: Z( r0 B1 M& o
                a = a ^ b;   // (1)
      R9 R6 t5 A# Y* o% P/ z  a0 o            b = a ^ b;   // (2)8 A1 t. q# B3 Y- S( [" F" I# @/ U9 H
                a = a ^ b;   // (3)( D. ]1 s8 Z# e: w! W$ }3 r
                printf("%d %d\n", a, b);
    7 m' |9 o$ [% X        }
    2 e4 [; {: E9 O/ b% g) L1 m! [        return 0;
    4 X# @( f1 ?7 o}
    ! r( X3 M2 N0 F: d8 Q+ Q8 i- `" d1, x" w4 |$ p& \9 _
    2  O3 V$ k) M3 p' f
    3
    1 N: n$ n! s& o5 ]4
    ' c/ R- q4 I8 ~; G5
    % _/ @% z/ R  h+ D# g9 R6
    - @8 ]6 v% E0 j# [7/ ~) W. V" V% q% O7 r
    8  n2 X! C5 G: o) q7 t1 x8 G
    94 n0 U" E7 v4 N  D' ^; ^; c
    10
    ; J7 O$ _4 R. W! p* O% Z2 K118 o+ j. }! i' m9 A2 P- E
    我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。9 ?% Q5 [$ Q* h! Y0 j9 l" K
    而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。5 y! Z" n6 S8 u2 ]1 _
    从而实现了变量a和b的交换。
    1 D+ S" R) _% f7 J  h& w2 f& V) f8 Q
      I  B! X! c, H4 M' h0 ^

    $ O& t/ u9 G8 R3 K7 J! U4、正确解法4:奇淫技巧3 U' Y2 F! r- T4 w  K. X; U% ^
    当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:
    ! ?. T$ D) J) _4 x! N#include <stdio.h>
    $ @2 X1 W+ S8 }- M* s7 k4 B: Pint main() {
    ( T0 `" @2 x8 p' [    int a, b;
    ( U6 h2 I) k) U# I! [$ |+ C7 R        while (scanf("%d %d", &a, &b) != EOF) {
    % O, n% L+ @( K) X            printf("%d %d\n", b, a);- l5 P% {" ~" Y5 g
            }
    " {+ a1 M; f$ d' E+ e& Q0 T        return 0;/ [9 k, m( _. y. J
    }& |/ f6 D3 J# b/ M4 G$ u6 u. B4 E4 f
    1
    ; v: L, l3 P" Z& R% n- V4 p2
    ! t3 _, o5 F2 v+ Y) o* y% g; J3: }; R. x; [0 H8 @$ d
    4; p5 A: t/ h  ?+ D" t! ^! j+ D
    5
    % N  C3 T3 f9 n: B6
    ! ^# f. P' q" X. Y1 k3 }7, J) A3 j7 W4 L8 C$ K
    8
    1 O. j$ S+ |0 Y1 U& c你学废了吗 &#129315;?5 d. h5 x  J2 `. E& g. o% }  g8 ]' |
    2、例题2:整数溢出
    : A# y" _' a* {( x1 H一、题目描述
    4 C. U9 T! J: I: _  先输入一个 t ( t ≤ 100 ) t (t \le 100)t(t≤100),然后输入 t tt 组数据。每组输入为 4 个正整数 a , b , c , d ( 0 ≤ a , b , c , d ≤ 2 62 ) a,b,c,d(0 \le a,b,c,d \le 2^{62})a,b,c,d(0≤a,b,c,d≤2
    / u: i; d4 i2 }62! I7 x* v1 N  G. z1 q: L8 K- }
    ),输出 a + b + c + d a+b+c+da+b+c+d 的值。
    1 g/ J4 N" i8 P8 `7 }$ J) @2 p, c8 h$ S

    ; z6 D' U- P2 m/ M2 c3 e二、解题思路
    9 B; P  C( L! c难度:&#128308;&#128308;⚪⚪⚪
    1 u/ N8 r, A, r- q" r- f+ T
    - Y1 `4 W/ d& _  B" C4 E; D; w. w
    4 X6 y: T# ?4 W$ H* G
    这个问题考察的是对补码的理解。$ b, Y6 y  I* v& u  |$ [1 |# P: m; D
    仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2 ; Z3 F2 |' b4 i
    62, m9 G0 _7 P# o5 i$ `- b* ]/ F
    ],这四个数加起来的和最大值为 2 64 2^{64}2 , l& |8 N) v" E1 c7 Z  P0 v
    64* ?; _; }4 [! U7 W* r9 Z8 I6 p
    。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12 4 z0 s0 S$ |4 p/ O) b9 x
    63
    2 {( c) n6 c# ^+ ~9 p" _( C −1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12 4 W& ~3 n4 v. q% n& ~! V: K
    64
    6 z  W' i+ X  r5 {) u  v4 _ −1。3 O/ `7 ~/ w1 z* M; s8 n' Z
    但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2 ; x0 _6 z9 o2 V7 ?7 A' _
    62
    6 T  C  |/ g; F7 r8 A8 ~  时,结果才为 2 64 2^{64}2
    $ [8 j, m& W5 A* r) K64) h' @  ?! p& c6 P  q# Y( ]1 @
    ,所以可以对这一种情况进行特殊判断,具体参考代码详解。! M# H! ~- ~+ y/ p" ?# u8 t% w
    三、代码详解2 C/ D- j1 `3 y" |
    #include <stdio.h>) Q4 f$ ?; R. q
    typedef unsigned long long ull;                           // (1)
    2 h) v) [" V3 X. T8 G( ]7 Pconst ull MAX = (((ull)1)<<62);                           // (2)
    3 j! U. b/ N  e$ K0 Z6 m
    0 U  q0 S7 q0 ~6 v, Z2 e) H. d

    5 _# o9 w0 N4 ^5 ~& ?int main() {$ Z% K+ S6 \# s; u/ \
            int t;) {" u9 e5 T( E8 L+ L+ ~: `; F
            ull a, b, c, d;& I/ @2 x# J7 [3 d0 l$ r
            scanf("%d", &t);5 q- V8 K- r  M$ G$ |
            while (t--) {
    ( ?5 l! z7 r7 C% [                scanf("%llu %llu %llu %llu", &a, &b, &c, &d);     // (3)
    3 O( L1 S( p/ S( x0 @                if (a == MAX && b == MAX && c == MAX && d == MAX) // (4)- l6 ]: h1 c; M
                            printf("18446744073709551616\n");             // (5)8 C8 }1 P" |* E( D3 S
                    else  n" C7 ~3 T6 I; D1 W
                            printf("%llu\n", a + b + c + d);              // (6)
    . O/ L7 q3 h( |( x: J6 T        }% S- M; c: i7 X( R
            return 0;
    . z! W& d. k# _- w# y3 @( X}
    6 `0 ]; K3 Q; ]1
    . R$ A7 L6 m  o1 a# j2% Z: |$ \  M' A, Z( v
    33 Y2 m. Y# t4 a! M
    4
    7 ^. p5 P# J3 T; m" s3 y' U5 x5: f, d+ p- L! p
    67 T( j+ `5 U7 ~
    7: D; f% A. }! R
    8
    % D/ q/ q3 g. [$ f91 j' w; j, F/ {3 A
    10: G/ z) L% Z. q, }
    11
    ( R$ o3 v$ o* B' {/ Q- r/ b12' v7 L3 E2 X+ m- P& G
    13/ ?# l2 ?' z* R+ c! B0 E% Z
    144 |6 h6 W: D- W" [8 v3 M
    15
    7 @1 P* k" m- z& L( o5 b16
    . R; F+ G2 A/ s, J5 F3 a( u9 C17! o6 Z% _) |1 f2 O1 k% W
    ( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;
    , m6 T7 p" W4 {6 E7 a( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2
    . m- t8 ]5 k1 E+ K! [62' V5 J2 w+ a6 p! R, Q# v# X% z
    ,这里采用左移运算符直接实现 2 22 是幂运算;
    / d! k: T0 I& K0 i7 \数学        C语言
    * J% b% Z0 _7 d% @. Y2 n 2^n2
    " R9 _" I/ y- o2 i7 P  q( on# p6 v* l8 d4 d% _) R/ k3 z
            1<<n
    3 k" @1 v+ T' D$ g& A2 p! R需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;( Z& h1 y0 Q, F. c, `, `/ a5 p
    ( 3 ) (3)(3) %llu是无符号64位整型的输入方式;: G$ i. N7 @. R5 \' a; w9 ^2 {
    ( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;
    0 p6 X* x" Z& H( 5 ) (5)(5) 由于 2 64 2^{64}2 8 O1 R. |9 V+ I- I, [( t9 j: \
    64- ]7 A6 i" p5 c
      是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;. K$ ?3 r! H! q+ ?( e8 F- X
    ( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2
    ( C9 d8 k; C1 O1 f, t2 E' Z64, X" t1 Q1 T4 |# z
    −1] 范围内,直接相加输出即可。1 }- {9 Y1 {9 l- t( `
    由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。
    * Z! T/ v, `" ^% ~为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 &#129315;。& v% n  n. [$ J4 b* l

    4 h* P1 d0 e2 P8 q1 M  d* b

    . a6 c* p" A+ w$ N1 p: C6 n3、数据结构
    + B3 X& L/ Q; a《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。9 ~$ F2 S) }: ]/ d" x4 W
    1、什么是数据结构4 g! ]5 g$ ^2 U3 g4 {9 h
    你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。
      B' o* P7 A$ a1 W: o+ g0 o' l如果一定要给出一个官方的解释,那么它就是:
    * _2 r! v8 t& {: ~) {  M计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。
    5 n& V& l6 {2 t% S! @7 Z$ ?% n9 V% r' L
    - F; [2 o! r/ _& h( K- u
    是不是还不如说它是堆,是栈,是队列呢?, q- w0 a0 G6 u1 P5 }! v1 y
    是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。* Q  _" H% \& k3 k
    2、数据结构和算法的关系
    0 N9 s' A- s, t很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。
    " W$ \' a, X. y+ |数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。
    4 n3 [* W$ b" T6 ]; F) L而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?. t& P5 e9 ]; x+ u" u2 w
    讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。
    : N. K, `: p- J  A, t3 x: }* B8 o3、数据结构概览
    6 a: X1 F0 f5 {周末花了一个下午整理的思维导图,数据结构:& ]3 a2 U7 g# Z5 ~: ]  P, Q1 [2 I
    & q! f' l/ ]. u- F% R1 O6 y1 T
    $ |8 R, _: U6 u
    常用的一些数据结构,各自有各自的优缺点,总结如下:( y" R# O& G* _
    a、数组
    , [* l) U/ g! C& W) T7 S2 r& ^内存结构:内存空间连续
    ) O& |) J; ]) e8 k# E实现难度:简单
    5 `2 V$ o$ P# Z# y7 u下标访问:支持
    % O  U/ p4 [+ i9 I" y9 q7 R分类:静态数组、动态数组
    " m" o: w. z- r! V插入时间复杂度:O ( n ) O(n)O(n)
    4 e. q0 V0 e4 L, q$ k. E8 S查找时间复杂度:O ( n ) O(n)O(n)8 a7 f5 ^9 F& B+ M5 _
    删除时间复杂度:O ( n ) O(n)O(n)
    * Q; v/ E( Y2 E. @
    / c* k* f$ M2 }* b9 [! Q

    2 s/ j. N( z# n; y! Pb、字符串2 g/ g1 f. p  ^% ]
    内存结构:内存空间连续,类似字符数组6 @  g7 ^6 }: @0 m5 z0 O( ?+ q
    实现难度:简单,一般系统会提供一些方便的字符串操作函数
    : O; G+ E' G9 W下标访问:支持
    # O; A  o) w9 I: _2 V/ R) M插入时间复杂度:O ( n ) O(n)O(n)/ b) y; {: x; b. H& ~# M( }
    查找时间复杂度:O ( n ) O(n)O(n)
    3 r$ C: H% i2 W. u# Q' h2 Z删除时间复杂度:O ( n ) O(n)O(n)
    , u& J6 A/ f1 e- {
    . I7 P; h4 f6 w% O: C6 S

    ' v+ X; A  z$ y8 v' `c、链表+ c# [0 s. h3 t  r. C
    内存结构:内存空间连续不连续,看具体实现
    3 W" O7 w: ^9 c3 ]: |实现难度:一般0 r8 _' h: ?5 S& Q1 X! r" j
    下标访问:不支持: l) N( q) z$ p$ }% ]3 D
    分类:单向链表、双向链表、循环链表、DancingLinks
    1 U1 g4 Q/ F( ]/ t7 j2 A/ C7 e0 w$ `插入时间复杂度:O ( 1 ) O(1)O(1)+ F5 C) f0 V' a, i
    查找时间复杂度:O ( n ) O(n)O(n)
    * \  M4 ]. B5 s3 p) J删除时间复杂度:O ( 1 ) O(1)O(1)5 H! |' X5 c. U  [+ r+ M
    , w+ ?# i5 c7 }* J# Q+ m$ v
    ' u! X" d: v1 N
    d、哈希表9 u+ ^& f  h0 ]  W6 U
    内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续
    # Y6 _; \" j" y) I' e实现难度:一般# @1 ^' O2 Z0 `5 `3 l
    下标访问:不支持
    ; O! G& I$ m( {& l: c0 s分类:正数哈希、字符串哈希、滚动哈希0 t* g; D9 P" B( S* X0 ^
    插入时间复杂度:O ( 1 ) O(1)O(1)
    6 O9 t# h# |$ {* Q9 K6 r查找时间复杂度:O ( 1 ) O(1)O(1)' H3 {. B: ]7 ]5 y! F
    删除时间复杂度:O ( 1 ) O(1)O(1)+ @% f4 A+ w+ ?9 ?7 b0 z# A

    9 j8 o" T2 [1 r) r0 U
    2 R* t+ C; ^) O' h2 {9 _
    e、队列
    * [1 P6 V) {: V& B2 Z# B内存结构:看用数组实现,还是链表实现
    $ P/ `* w  j+ S; n" v% R2 E* c实现难度:一般
    1 F, b" l. `0 E下标访问:不支持! {0 G' N: E1 q6 L. B8 |4 o) c3 x
    分类:FIFO、单调队列、双端队列
    ; i" l& s) G2 K2 N) N/ k$ e3 M. q插入时间复杂度:O ( 1 ) O(1)O(1), G+ k" E# U- n2 }3 Y9 m' I5 ?" i
    查找时间复杂度:理论上不支持0 K+ q9 Y, Q, j( |0 v0 B
    删除时间复杂度:O ( 1 ) O(1)O(1)
    9 c; n$ o$ {0 ?/ _% e* r
    & g: o/ g* s  c1 F1 W) Q2 \

    2 q9 U  Y: P" w8 ~5 i# x+ L( af、栈
    1 o6 x4 S4 D! O, q) w( o内存结构:看用数组实现,还是链表实现
    2 l4 T& [1 O9 S1 _: {4 S实现难度:一般8 z  J* Z( r* f! `( t4 P  w6 f
    下标访问:不支持
    $ n5 m  b9 b1 a* u7 z& ]! k分类:FILO、单调栈; G$ d2 r0 P8 Z: ?
    插入时间复杂度:O ( 1 ) O(1)O(1)% R) {0 p  b( h8 @1 \% M. d% Z
    查找时间复杂度:理论上不支持
    4 B5 H' ?8 p/ E; }' O4 t删除时间复杂度:O ( 1 ) O(1)O(1)
    4 V$ Y/ n# H9 ^0 P2 o
    ; w% p% \9 ]% K4 q, }' v% S7 h

    * {% v3 a- u- H( s& L. ]; Pg、树- K  \7 D& v( y  r
    内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续1 o$ N/ x* g$ T2 B$ y
    实现难度:较难1 a6 N% @* x* E6 b) k; f
    下标访问:不支持
    9 A" ]% @9 r: p+ G; `2 T分类:二叉树 和 多叉树! a( a2 C# S  r" F6 `3 W
    插入时间复杂度:看情况而定
    - |, p$ g  Y' L/ }" V查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log / `& N& c7 }' [8 M
    2
    ) e& x' c6 h( P7 U  l​       
    - J, D; o. ~3 u1 r n)2 l% N2 }% w- {8 B; B" T% [" o
    删除时间复杂度:看情况而定6 ^6 B# D7 e& f  [/ r
    - f) y1 `1 s+ L- `. X
    0 R! U# ]" G3 s
    1、二叉树5 L0 a" A' Q4 p- Q2 S! y- }
    二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。
    1 C3 e  Z- I2 S其中,堆也是一种二叉树,也就是我们常说的优先队列。9 R0 k8 c# [( Z8 v. d
    2、多叉树1 R# L; e( A# h3 k  w5 a8 r
    B树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。
    ' K5 M6 O( |- J. n+ ]h、图
    ) K7 l; ]$ w" Z1 e5 Z0 b2 q1 W内存结构:不一定7 _: r, |! j4 [9 i% O/ ?
    实现难度:难
    * J9 u: {. M- k1 W下标访问:不支持9 S. I9 v$ Q  `# V: b( O2 i5 o$ K
    分类:有向图、无向图
    . |4 U1 @3 z% f3 R; z' P" G插入时间复杂度:根据算法而定
    / `3 }+ f7 k, o* t0 z* X查找时间复杂度:根据算法而定
    / m! _0 D/ v) B# c删除时间复杂度:根据算法而定
    ( v0 \( K/ H0 y( J4 I6 u3 J4 X6 I& H. U8 [3 f  o& S: c, D
    # O5 r" Z0 p+ [+ y* i7 M+ M
    1、图的概念, y) {/ {% g0 a9 N+ f( p
    在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:
    # L' F  a+ r- k! x; k& Y9 }图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。3 }# {1 ~2 m: ~, A- X
    对于无权图,边由二元组 ( u , v ) (u,v)(u,v) 表示,其中 u , v ∈ V u, v \in Vu,v∈V。对于带权图,边由三元组 ( u , v , w ) (u,v, w)(u,v,w) 表示,其中 u , v ∈ V u, v \in Vu,v∈V,w ww 为权值,可以是任意类型。
    + G/ A2 F* H8 Q& R" D  B9 z- A图分为有向图和无向图,对于有向图, ( u , v ) (u, v)(u,v) 表示的是 从顶点 u uu 到 顶点 v vv 的边,即 u → v u \to vu→v;对于无向图,( u , v ) (u, v)(u,v) 可以理解成两条边,一条是 从顶点 u uu 到 顶点 v vv 的边,即 u → v u \to vu→v,另一条是从顶点 v vv 到 顶点 u uu 的边,即 v → u v \to uv→u;0 v8 Y, f  g$ p) ~; }
    2、图的存储
      G/ K$ l0 h, ^# W" a% z对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。
    $ c# Z" B7 w: G! h0 n5 ]' l$ j
    & c6 h7 W+ j0 L$ f+ U6 Z. ^. w* q9 c
    . G# P; v& t* _
    1)邻接矩阵8 b0 D5 o7 y+ `/ M* r3 k- N0 {" w
    邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。. Q8 G, O' U# S
    它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。7 Y" t2 }( t9 J* e( b8 Q/ P
    [ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[7 X0 a8 ^8 C/ Q3 Z' q4 w
    01∞9∞0∞8320∞∞∞30
    . @% Q7 x  j! ]0∞3∞102∞∞∞0398∞0& ~/ R0 H+ C$ `0 o+ K$ x$ S  ]2 R
    \right]
    9 ~1 M% r8 @, X7 {9 z- i; B8 A* M! y: Y
    " h" Y' }5 R9 Y$ {$ _5 z; {4 i, v$ o9 S) s
    6 k( a: |7 J% y
    & ]; e$ q' y0 x- X2 o
    ​       
    ; E1 H1 {# c" V  
    ; m9 N/ ~4 k% A* G( Z% y8 |01 E4 Q5 W9 I1 O8 ?
    1  Q* k( _1 l3 K+ c' ~0 @& x

    $ A: I! L' R% v1 k; F* f9" I0 ]# X9 M* N* F% I
    ​        0 {+ P7 A! f! [) R. `% B0 Z
      
    ( L0 B3 b1 }% K/ g% t8 M! R9 H- R2 |: V% ]8 H. ?3 Z7 n
    0& D9 D2 s# U* |2 e
    ( Q8 |1 {1 y# i
    83 N$ I! g/ B7 F9 V
    ​       
    1 F2 b/ l3 L4 S4 o  + r0 O$ a& j5 O. @9 X! f$ U
    3
    ! q" u3 v! k+ i1 |2
    8 v% @1 a2 k6 S0 N  x0
    9 b. n% q9 q+ K5 ^4 T
    + P0 M5 u7 C# `1 w# C​       
    ) R: M7 n$ P! Z" n! G$ u  7 u+ i. g" d$ |' c3 I4 @

    2 D# x3 k0 u! ]: I8 ^$ s
    8 T) z1 n7 S$ o9 B7 g: Q+ `3
    3 R# H6 O& w7 \6 p% j  c/ g' G" R07 L% b% G) i' y+ t
    ​        3 a" @0 w7 o& U0 C6 b1 V- T
      . x. I2 ^% X( I& S: K! ]

    ) \; t  x- K) R
    + M& {0 }: T" P4 }; i
    5 [, {2 z1 o! L2 r) A5 V
    $ ?; @1 L( {1 c( Q* A​        ! J) p& U& X0 E

      u$ `9 J: M: s# r2)邻接表" n/ U+ x( O0 k- _* x2 v
    邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。6 I3 ~% @- K% |) e4 D+ C# G# c
    它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。
    * X9 m' f. ]! u2 z/ Y- ^! S& |如图所示,d a t a datadata 即 ( v , w ) (v, w)(v,w) 二元组,代表和对应顶点 u uu 直接相连的顶点数据,w ww 代表 u → v u \to vu→v 的边权,n e x t nextnext 是一个指针,指向下一个 ( v , w ) (v, w)(v,w) 二元组。
    ! M  E& X; X9 ?0 V! }& y5 W- y4 A
    $ F8 P3 i  i; _" e# `
    7 U! P7 c+ }" f
    在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;
    " d0 C- P# K; z: A) U    vector<Edge> edges[maxn];/ I+ p) J  o2 |/ f7 v% W5 h+ c& j
    16 }, y1 r; {" D3 ]
    3)前向星
    * e3 j* |: y6 [/ m前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。. V% J$ [: Q3 a9 t
    它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。
    7 ]$ o8 D9 d2 ^* J, ~3 M1 y如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。
    # B! N- ~  f4 @; G2 X
    $ G& C. f' i3 k3 f
    2 m  v1 P7 F. j
    那么用哪种数据结构才能满足所有图的需求呢?  V5 D: K  q& l1 k( B; x" E) F
    接下来介绍一种新的数据结构 —— 链式前向星。8 I4 n) K% d+ V
    4)链式前向星
    ! g& r+ Y% Y6 `链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 i ii 都有一个链表,链表的所有数据是从 i ii 出发的所有边的集合(对比邻接表存的是顶点集合),边的表示为一个四元组 ( u , v , w , n e x t ) (u, v, w, next)(u,v,w,next),其中 ( u , v ) (u, v)(u,v) 代表该条边的有向顶点对 u → v u \to vu→v,w ww 代表边上的权值,n e x t nextnext 指向下一条边。; o1 W; J% V9 q1 A
    具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。2 A! a8 k0 {, O$ V7 `3 P" A
    边的结构体声明如下:
      C* Q: V3 o9 K- O6 {, fstruct Edge {/ c, ?& R: o: h* D$ I' v
        int u, v, w, next;: o$ Z+ E) @$ B4 q
        Edge() {}& `- {6 A! U  C. g( L
        Edge(int _u, int _v, int _w, int _next) :
    & h. M! B, p  B! U, J& [& f% S9 N8 {        u(_u), v(_v), w(_w), next(_next) , d  t" L1 W7 v) a3 l
        {1 H$ \" e, I1 V( S9 i$ m
        }/ k6 D4 z" D; r
    }edge[maxm];7 g% Z. W3 S1 L' [5 `! a( c
    1" v- v0 M  Z. ?/ R2 z3 R7 k
    2. ^2 n" j+ _0 J; B
    3
    # v2 q4 K. ]4 R& ^) ]8 _4
    " l1 T. O' s; \, |/ p3 c+ ~: s* q( k5- x& p+ D4 r/ U) ~4 `0 D* |
    6" M6 F) V  }0 f
    7" X2 i, P! q3 {  I. q" f; a8 I
    8+ o& G: C" j; I2 [$ h
    初始化所有的head = -1,当前边总数 edgeCount = 0;
    ; S1 u( C0 o& p  y; E% S7 W- h每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:2 Q) g7 [' Y8 C( z
    void addEdge(int u, int v, int w) {. W3 P) P8 C( S+ i  w! Q4 X* q
        edge[edgeCount] = Edge(u, v, w, head);9 |  \6 G. i1 c! j1 c" ?
        head = edgeCount++;9 c4 k) g* ]; _3 l1 S
    }
    3 R% \, |7 ^/ ^' J+ t! d( l1
    ! J# C9 e/ f2 o* f  I2 P0 X2
    ! A* ?; A- H) J& V2 H" I& I3
    8 V1 `- T5 }( _. ?" r" u5 n# U4% K5 _6 A5 U" ?  h# K& h5 c9 g
    这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。8 _) \: a2 k/ V3 h6 w
    调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。
    5 i7 o. u3 [0 gfor (int e = head; ~e; e = edges[e].next) {5 V! k6 @( q; _- I# N) o8 H
        int v = edges[e].v;, P) B3 V9 r, \0 r( x
        ValueType w = edges[e].w;
    & ?5 C) Q$ W5 M0 @) L7 [4 s    ...$ ]( Z1 e0 O( _( V' s
    }9 _4 T& g/ \( u! G& v3 V
    11 O& T. Y9 V, D
    2
    $ n4 E" c! G; z8 R' p3
      _  e* W5 Z% ^2 p7 u7 y5 k4
    8 h  n% B0 a; X+ |8 w: ?4 B5
    " i& Z( X" q4 Y7 E文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。
    ' k8 J, @9 O% K4、算法入门
    8 H/ x& }: I) P算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。$ e: H& j0 J- P8 n4 z( a# i0 H

    1 }3 R/ T% l+ W
    : y+ l) \" m3 [7 q" v* V2 e
    入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。
    + L1 W# X% v( ~% p+ |( u  \对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。" O3 j% T5 k. X: q4 H
    1、枚举9 L5 o  v9 e8 h4 b. k* l
    枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。
    ( X% k: ^6 r+ v5 Y2 P' G对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。8 V9 C! W7 b4 X; L
    2、排序7 ]# A8 w0 `7 r  [/ Z# U) U& `" e0 S
    既然是入门,千万不要去看快排、希尔排序这种冷门排序。- H. n, ^+ @% B& N& m
    冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。* b9 U7 C: M8 k) H& a
    C中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。2 ]- e) ~: Q: t7 w* m8 B1 O
    3、模拟
    5 R8 d9 F) G) C5 s模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。
    * x4 x; Y% \4 s. g不管时间复杂度 和 空间复杂度,放手去做!3 h8 n$ O7 Q& D
    但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。
    / F: `! |; ?0 p7 S" w3 x4、二分
    5 E+ n4 {7 u0 E' v( \  i3 J$ ]! M* J二分一般指二分查找,当然有时候也指代二分枚举。% \1 X" G+ T) s5 i- }) L
    例如,在一个有序数组中查找值,我们一般这个干:
    ; y2 v) T9 s0 o& e- J6 a# W1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;
    ' I2 U6 @: H% [4 Y" r) h2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:+ z. L, G2 P- m# P4 z/ Q% t# R
      2.a)目标值 等于 数组元素,直接返回 m i d midmid;2 m* z+ v* k) b: f7 |& E+ j
      2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;
    6 G+ @$ t. U( S7 Y; l: X# |  2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;; u2 g# C  B# n* v
    3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。
    - j2 r, g: T; c5、双指针5 k8 Z& i( V1 N5 V5 T% j
    双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。- T6 C$ W0 N  R0 X  c, g& F
    : ~# M/ N: d! ]/ s5 @

    8 o) H, t1 u; j3 F* [. }2 T/ x' Q6、差分法! T' ~/ z  A% T% J4 H
    差分法一般配合前缀和。
    # k: |- \+ W8 S* i4 R5 k对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;$ A  D2 W8 t' ?0 u3 u
    假设 [ 0 , x ] [0, x][0,x] 内的 g o o d   n u m b e r good \ numbergood number 数量为 g x g_xg
    9 W# D, q0 F/ G% Q. O0 qx# ^/ V( I7 S7 r& k3 a4 G
    ​        ; l$ h' @1 I' s7 K) Z
    ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g 8 a6 ^6 J: v/ m: ^" k7 G  Y
    r
    ! F/ t; F6 R) F​       
    5 m* {5 E7 o, p1 ~ −g 6 B$ u: o3 c, C2 j+ v
    l−1
    7 [8 C2 W" C( S! ~* S​       
    0 k( P' r3 @& K& G- b; O4 f+ O ;分别用同样的方法求出 g r g_rg
    ( ]" v$ ?9 q) q9 E! `- P2 m7 Xr
    ) G9 e( v2 M) j: X! V/ A" M​        , Y2 @* ?( c2 C6 L1 R3 f
      和 g l − 1 g_{l-1}g 4 L2 O. v2 v0 A8 E- G* x8 U; T4 I2 X
    l−1
    + Y! s/ _, Z. T4 i7 d​        0 t) D/ o/ K( L
    ,再相减即可;1 T4 n  \6 v$ i: B6 ?% D
    8 ]1 C* i" ]; \5 K. v; d+ Y. t

    # E8 l7 g! \0 ^' c7 e7、位运算
    ; S) l2 p7 h. @, R位运算可以理解成对二进制数字上的每一个位进行操作的运算。; ?  L% x: D& _, \
    位运算分为 布尔位运算符 和 移位位运算符。2 \* A5 i( o. `
    布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。
    9 D" S$ o% p4 a如图所示:
      ?1 u( Z$ b% c& q; W3 v) g- H4 b. y- z/ M* n7 G5 X

    ; \5 {  M9 @5 V* {位运算的特点是语句短,但是可以干大事!
    8 I" I% g3 p! Q9 m6 A比如,请用一句话来判断一个数是否是2的幂,代码如下:/ j0 x+ c+ J* d; u
    !(x & (x - 1))
    $ d, V/ |8 N/ p1
    6 B, ^# D; [& V; L' |- F8、贪心  O% s0 C4 N2 A4 L9 h& q
    贪心,一般就是按照当前最优解,去推算全局最优解。
    % g; a* J& U; A" h所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。0 ?. J! y  \) t  q
    9、迭代5 N- {; h7 p4 X
    每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。# g6 y$ o& z  @# T' z
    10、分治
    " u2 S& {1 h" y分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。
    * [5 {! F- Q3 j% S7 v2 N* e5、算法进阶6 D- A: h" W. S# t- J; `
    算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。4 b' j% V2 c- \4 M: t
    如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。6 p( K+ ^# S2 g& K$ |
    这个系列主要分为以下几个大块内容:% T! E) J5 R# O* e0 F
      1)图论1 Z7 p3 z! `9 h" Z5 t
      2)动态规划
    3 E7 h- L' f, [7 b) \4 F0 X0 \( G  3)计算几何
    + y( K/ s1 c6 B# b3 O  4)数论7 d6 [/ [# C1 h: {$ U9 j# @+ B0 v% O( u
      5)字符串匹配" P# S8 z" u6 X# o+ ~' S
      6)高级数据结构(课本上学不到的)5 J- H2 ?3 m( w+ C$ h& i! \
      7)杂项算法
    # l1 i7 Y& ?6 Y1 }1 \# F
    - I+ C4 @  ^4 A* R. z1 ?

    ( T9 q& a$ Y- U0 f$ P* ]% y- {" B先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:
    5 Y( Y% E$ I% W1 ]8 r) e) S
    $ h7 {2 @4 v; a; d) F- T7 Z5 v

    , O. [# H, b) L& G9 Q5 L; e% N
    : v0 O6 b! ~5 B1 P0 k

    & o- A1 L$ k6 |1)图论  z2 T! H) i/ v# x0 s: y
    1、搜索概览1 r6 S! G' \/ F; |3 R% G
    图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。  A0 p2 Q' ?! m' E9 s1 p5 ~/ s

    * J9 _* ]7 f8 L7 N! R

    0 K! o0 m' C! V+ e比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。
    . ?( |2 W# K- R+ u1 A. X+ U. w2、深度优先搜索/ v9 {4 O( y! K/ X
    深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;4 d; |0 U9 ^' F8 w1 s9 I
    原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。, U3 S& y7 w% G$ A% r
    但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。
    ! T. z( o) c, }3 m4 q0 s! c如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。% {' }+ V" t) m, ~/ R7 c
    $ G% V$ J4 f- E2 H5 d9 d# H( c8 _
    * l+ d0 b. Q/ G) N# ]/ m, V1 b
    红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:- V2 U# N9 m8 r# \7 Q2 X
            0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 6- u/ n: m( t+ l" x- q
    1
    $ ?+ B! {& {% q+ f5 Y7 Z+ l同样,搜索的例子还有:
    2 f7 N8 Y; m) n, q" Z5 p' v
    . |# F7 j. l% a

    6 I! u9 U9 r9 _4 d计算的是利用递归实现的 n nn 的阶乘。
    9 o: B* `& Y" ~: i( o9 G3、记忆化搜索+ `. N2 ?0 _: J* X
    对于斐波那契函数的求解,如下所示:7 H1 h0 D5 {/ O- ~4 r8 y
    f ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =
    3 h" ]% {3 F' M2 O8 b8 E⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)
      Y( l, F+ q  h# i9 t: O; |4 }, n{1(n=0)1(n=1)f(n−1)+f(n−2)(n>2)3 k, F( c$ \- m1 X
    f(n)=
    ( ~/ a8 q) \& G
    ) W2 G$ K, ~6 V' o3 M3 j* r" I3 D# u$ H5 A5 H8 F$ e

    ( v, E2 B) }$ N1 K$ ^5 `( }/ k; o5 b* N  R! }3 M  G+ C) p

    5 @; k1 N, t% a) K/ i+ H; z- E5 Q) |​        ! {! r2 V. ~! }( L
      
    * @1 a; T3 j  i3 z0 R1
    . F0 r3 c1 c) C) }5 ~' r4 b1
    / V& z' X6 \/ Y: I/ s  c! v! Rf(n−1)+f(n−2)2 ]+ [5 V) @! |9 x' A/ m/ f
    ​       
    $ ]% Z  r5 e& x# G$ l  1 h) W$ s$ I  n' c; X) N6 I7 _5 r$ y
    (n=0)
    3 c) ?! O7 _2 e# ]6 i7 u5 g! \/ q" ~(n=1)
    % S4 v, d9 g' a  n9 K) k(n>2)1 F0 S, O0 y; U/ p3 r
    ​        4 I2 G2 q4 J% I; T3 B
    - S" _' H6 A) ~% N# Y! X5 T
    对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:
    0 I% a$ f$ J/ ]" }* k- f, }% A: b$ a" m. W3 Z

    " u3 B# Y0 o  i# p2 l+ R# V8 d2 O, K这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。
    5 y9 B6 ^# W$ n8 C我们通过一个动图来感受一下:
      X; t7 f! z3 [& y! \% X
    8 S) I  w6 o; a, b! j  y

    7 B  N; R$ H! x1 [( J5 d: e8 I. M当第二次需要计算 f ( 2 ) f(2)f(2) 和 f ( 3 ) f(3)f(3) 时,由于结果已经计算出来并且存储在 h [ 2 ] h[2]h[2] 和 h [ 3 ] h[3]h[3] 中,所以上面这段代码的fib != inf表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。, w, T2 [: T5 @& ^7 x/ J
    这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。+ [  U: z* @3 P) `& {9 \+ ]
    4、广度优先搜索
    8 Y7 I1 K; @. C: @. h单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。: a. p7 X4 U* ]4 U! C' |
    我们通过一个动图来对广搜有一个初步的印象。
    ! G8 N, J' E; ~# z) p/ D* P4 R9 \  v3 v( O0 @- A

    + I, M* C2 g# L0 D6 E* e$ Y/ ]" Q$ ]) j1 a2 H# O- Y7 I

    ; N9 x( P  q; j# y' B从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。
    & ^7 ]" l: a3 z& h) H. k# _那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。
    ' q" o' Y& _/ `7 j这时候,算法和数据结构就完美结合了。+ g' q6 l/ @% {# @2 F
    2)动态规划
    5 J1 D# h3 X# b动态规划算法三要素:
    6 A4 I  r; [5 P. s! a/ J2 ~  ①所有不同的子问题组成的表;, a! e4 U' |' i1 t2 a$ G% B
      ②解决问题的依赖关系可以看成是一个图;
    ( n7 v7 C6 R# Q9 C/ `7 L# |9 E) y  ③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);8 ]( u6 y4 h! n: j
    8 W9 E; \4 [% O# n
    8 y% |- [. Y# }0 T7 o: a6 o$ Z- F4 C
    如果子问题的数目为 O ( n t ) O(n^t)O(n
    4 w2 X7 ?* r! At* V9 Y3 j  \0 o* ?( ]! j. |& l! D
    ),每个子问题需要用到 O ( n e ) O(n^e)O(n , i" ^( e( D) J* d( o9 r
    e
    1 z4 B5 y3 p* H. k/ n# F. c4 d ) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。
    + t6 L2 P5 f& R0 m- }1、1D/1D
    / V/ E- d1 q0 e1 v7 M( Ud [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j )
    $ L1 p: T3 h- f: U' Qd=opt(d[j]+w(j,i)∣0<=i<j)
    7 G5 u# F" [0 X5 G$ M: N! d状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):
    , C  w0 W  p, u0 }/ a: l) x+ i* \
    ! J1 J" v) m( j

    6 G# u% D/ Q' f5 D9 Q& T8 e这类状态转移方程一般出现在线性模型中。
    ' U; F( Q# Z; o( S2、2D/0D0 J2 j* w4 e% }% a4 i
    d [ i ] [ j ] = o p t ( d [ i − 1 ] [ j ] + x i , d [ i ] [ j − 1 ] + y j , d [ i − 1 ] [ j − 1 ] + z i j ) d[j] = opt( d[i-1][j] + x_i, d[j-1] + y_j, d[i-1][j-1] + z_{ij} )/ W  n: O+ X: l; x; n
    d[j]=opt(d[i−1][j]+x 8 \( J+ g, N* E. c* Z/ L0 @6 N
    i$ J9 L( k! {4 b( ?5 N
    ​       
    7 g6 h. q9 G7 l  q ,d[j−1]+y
    ; D) q( l& N! r# ^9 ~3 Jj
    & \! C+ Q, K. C. T( {​       
    : v( f: U8 x. l+ X ,d[i−1][j−1]+z ) f8 F- x# L1 q3 @0 h3 ^
    ij! r- L8 }% H1 t: M. _
    ​        + o( p1 p& T% d4 M
    )
    7 |3 D7 [9 S) ~状态转移如图四所示:
    ! }9 d3 r5 p+ o) q% Q( ]; o  l& s$ j0 v' T6 B' ^2 n) b

    . Z+ |4 R( i  \比较经典的问题是最长公共子序列、最小编辑距离。
    " _+ u' O' g' |9 t# g: b! ^有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列8 N$ S) D+ X: `3 L
    有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离- F; @" j7 H/ D! H
    3、2D/1D) X1 O( |) A  R& h
    d [ i ] [ j ] = w ( i , j ) + o p t ( d [ i ] [ k − 1 ] + d [ k ] [ j ] ) d[j] = w(i, j) + opt( d[k-1] + d[k][j] ); }. |: [' G, J6 f. w5 l
    d[j]=w(i,j)+opt(d[k−1]+d[k][j])
    ; u2 J6 S+ u1 ^0 u区间模型常用方程,如图所示:
    ) R) h5 w! b& Y% n: u# }
    , g9 b+ j! \# k

    " T9 ?! M! G9 s9 S1 t8 b另外一种常用的 2D/1D 的方程为:, z- C: K4 W/ ~9 M
    d [ i ] [ j ] = o p t ( d [ i − 1 ] [ k ] + w ( i , j , k ) ∣ k < j ) d[j] = opt( d[i-1][k] + w(i, j, k) | k < j )- J8 {2 Q! ~& r( M2 ^- M" Z$ g3 ^
    d[j]=opt(d[i−1][k]+w(i,j,k)∣k<j)& {' |# p; l& W' Z+ `- U% G
    区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP
    * |* b+ ^5 e6 y5 G3 P$ M4、2D/2D# z8 f* D3 t0 f- X  @
    d [ i ] [ j ] = o p t ( d [ i ′ ] [ j ′ ] + w ( i ′ , j ′ , i , j ) ∣ 0 < = i ′ < i , 0 < = j ′ < j ) d[j] = opt( d[i'][j'] + w(i', j', i, j) | 0 <= i' < i, 0 <= j' < j)3 ~' {* ^0 T) f8 k% ]3 q) ?5 Z
    d[j]=opt(d[i / j2 y5 u5 E7 W" X5 R4 f+ Q0 z
    # c. L2 `( _) |% V7 A3 L+ k/ o
    ][j 9 d$ J  c1 p& t. k- G
    8 O6 m- o+ {( M/ r0 ~$ l
    ]+w(i 8 {# y) K4 s1 F3 ~2 A8 ^* o& y* ]
    * G+ N' T/ i6 v  O% D
    ,j
    1 d( R* z* \$ \4 V& s. j. `
    % ~+ g7 I4 b2 G ,i,j)∣0<=i 0 c2 N! H3 {1 a7 s1 j5 \7 [

    ; d  o% D5 f  P5 b. M <i,0<=j   R9 x( w% b. y% O

    ) g) g$ ?& C! v0 ^. B7 M <j)" V1 b% U% ~( v1 v* x5 }4 {
    如图所示:3 J' j9 V) w3 ~+ ]2 L) T; c2 x5 |& P' r

    , i! ?+ I# d% L+ _1 N' h! E% y- L$ h
    - e: E; `+ \( ~
    常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。
    " L! H$ h( @# W& B对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n & }% X% v% `) e8 g" R% _+ m* A6 q( y
    t+e
    ) @0 q- y8 u+ @; N; h ),空间复杂度是O ( n t ) O(n^t)O(n
    7 M4 X7 n5 E3 h( e* ?9 m& w6 ut' G0 n5 J! R* W" `
    ) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。
    1 ]$ q4 V9 X+ j% y: h+ d3)计算几何
    " x/ P1 e+ W/ u计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。
    3 F) m' Q+ _8 q2 j8 z. u6 q如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。
    1 A* p3 O. p4 C8 T- D4 Z1、double 代替 float
    5 h# k; z" H/ P( p: dc++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;
    # z% C8 L% ?! O% c2、浮点数判定8 N0 D: p6 N2 }0 O3 d1 W/ o$ W
    由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);
    8 V( J( [' L! V( z两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:
    5 G2 w4 n3 x* ^9 C4 T' F" P# ?const double eps = 1e-8;
    " R7 b9 B1 {  I% r! U1 Rbool EQ(double a, double b) {; w/ N- r5 q( m
        return fabs(a - b) < eps;
    6 @& S# P9 U, r8 q}
    ! F* i9 Z% A& y2 \# y9 U1
    $ L6 ?6 e; h: x7 E- Z2$ u! _. ]6 W$ @3 Z) I1 s& _
    3# G  ~. T1 l8 [% A. ]: ?4 G
    4
    4 Y/ o& |9 I+ r1 Z并且可以用一个三值函数来确定某个数是零、大于零还是小于零:. t2 l4 S+ g/ W5 b9 q, q/ n
    int threeValue(double d) {+ D' _" @5 C! o& q
        if (fabs(d) < eps)$ L$ T* a& q% Y  ^
            return 0;
    ' H6 L2 E; v+ i, w2 M) ^    return d > 0 ? 1 : -1;
    , I  y  t8 t( d+ `  G' P* q}
    8 c6 m# z) s& A4 T# o, M6 V1
    0 v( Z) V' z2 ]; L% c2: s. s* u" d, c  F5 K
    3, Q8 [8 r3 g( m& J0 S0 `
    4' ^1 p6 l- i) N, |$ k3 u/ g& H
    5
    ! ?# v0 V7 i# f) \3、负零判定+ A# y9 ?( X6 I9 w+ s. i
    因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:
    ) [6 \0 [2 h6 F% s7 U& c' N2 I    double v = -0.0000000001;
    7 n* l$ W+ T4 F    printf("%.2lf\n", v);& w3 c# U2 Q9 l8 ]
    1
    # H! r  Y# O! y" O: |2; w3 A8 `; W/ Q9 d* G) w
    避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:# i7 C# B/ M  O
        double v = -0.0000000001;
    5 y4 H' B. c6 Y$ k8 {    if(threeValue(v) == 0) {# z4 U- {* j& d! l9 B( q
            v = fabs(v);
    ) d5 @' q. N$ z# `! K1 M    }. P) t; I$ G  A% Y$ q
        printf("%.2lf\n", v);
      Z( C7 o1 v* C/ y1 n1% H- P* b% }" h- ^+ w: G
    2# \/ J4 p7 h2 C& Y8 H$ w
    3
    ; c6 Q( y8 Q3 E, v; E4
    1 J/ d7 D, m5 E# V/ {5 b4 q5
    5 v. F8 \8 \2 h4、避免三角函数、对数、开方、除法等' N4 _1 I" F5 F* x- B
    c++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。: `& p7 h9 J* b& J, e1 k; X  }
    除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。( ^% a4 x9 [& A: M/ G9 A6 n8 \6 i
    5、系统性的学习
    9 a% \6 [6 h9 e2 N) d5 I9 N0 l基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;. y: P1 K& A- a5 Y1 @+ K8 ~
    进阶知识:多边形面积、凸多边形判定、点在多边形内判定;- j( S+ q2 H3 T# r4 W
    相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。- ?' ^9 S. c7 h/ X  U
    8 ]/ A1 i1 B* ^* c5 Y

    * U/ U+ z  P1 t* Y6 O学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。
    0 s9 c* @( q# v4)数论
    1 o: M8 L- A* s. g5 f8 W* w刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!
    ) w( p1 b% s( b! V4 O数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。) R; I+ h6 P6 t' N
    当然,数论也有简单问题,一般先做一些入门题提升信心。
    " j- y; L' n+ O, i1、数论入门
    . f3 ?8 c, M5 u! ~2 E+ ^# V主要是一些基本概念,诸如:
    $ q% j$ }6 Y+ w% g4 p# u整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;7 N0 r: y8 E3 c
    2、数论四大定理1 v6 L- x8 c  Z: F' F  u, h+ m
    这四个定理学完,可以KO很多题:
    3 w; s# U9 h+ S( [$ x欧拉定理、中国剩余定理、费马小定理、威尔逊定理, J8 @. i  o  T# ~
    3、数论进阶$ R$ R% }* m2 ~# o# F5 V
    系统性的学习,基本也就这些内容了:
    7 x. s% Z. j2 m( ~! k扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。
    2 V1 w" Z- {3 H6 c( p  a5)字符串匹配( I: }0 \+ F1 X5 t$ {, r
    字符串匹配学习路线比较明确。
    8 _+ [$ C; T! j! O+ O先学习前缀匹配:字典树。
    6 j) |& `% u1 u! b2 a然后可以简单看一下回文串判定算法:Manacher。, ~* o- e# {  o: o: e- j7 M
    以及经典的单字符串匹配算法:KMP。8 U& n" r, p$ d' x. i$ A+ b
    实际上平时最常用的还是 BM 算法,而ACM中基本不考察。
    2 m$ R: h* P7 H. z然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。- O5 ~' E0 h2 @7 d
    关于 算法学习路线 的内容到这里就结束了。
    $ n% I3 ]4 H" \1 ]/ R3 G9 O如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。
    ( u! K! A) M+ l  I8 W4 C' l7 {参考资料
    8 F# e, b8 H# g0 M- ^( i6 M, W+ q【阶段一】C语言学习资料:《光天化日学C语言》(日更)4 O3 p7 p0 @7 |1 w
    【阶段二】C语言例题:《C语言入门100例》(日更)  ~% O" b) v( h& J+ F
    【阶段三】算法入门题集:《LeetCode算法全集》(日更)7 x& `& M2 ~+ D8 S" Z2 h/ M
    【阶段四】算法进阶:《夜深人静写算法》(周更)" {' s" M' i2 {
    ————————————————
    / a6 _& {9 @8 t/ z版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。  f/ l. }* L" g6 `
    原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/1183822282 Q' m. ~: C5 A5 A; @$ H2 u# T% u

    , L3 i9 Z+ x' f$ J9 P- N- N* I+ W. ?% O' J1 B5 y& N
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信

    0

    主题

    10

    听众

    299

    积分

    升级  99.5%

  • TA的每日心情
    开心
    2023-10-14 10:28
  • 签到天数: 28 天

    [LV.4]偶尔看看III

    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-8-1 02:48 , Processed in 0.516994 second(s), 56 queries .

    回顶部