QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 4445|回复: 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

    / Z& i' B# n3 a& q" W❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)
    ! x% n  d: Y  y8 M7 c& c8 U5 ]3 W% M, J: ?0 h8 k5 O
    前言/ Z8 s  V8 N0 a
      所谓活到老,学到老,虽然我感觉自己已经学了很多算法了,但是昨天熬夜整理完以后发现,自己还是个弟弟,实在忍不住了,打算把 算法学习路线 发出来,我把整个算法学习的阶段总结成了五个步骤,分别为: 基础语法学习(重要)、语法配套练习、数据结构、算法入门、算法进阶。本文梳理了这五个大项的思维导图,在下文会有详细介绍。0 j+ @2 v% s( N5 X6 l$ C% D1 ?; T
      希望各位能够找到自己的定位,通过自己的努力在算法这条路上越走越远。
    0 P! k: [  Q6 M6 R8 y7 M/ E$ s  刚开始切勿心浮气躁,千万不要给自己立 flag,说一定要把这么多东西都学会。就算你的精力旺盛,日夜操劳,时间也是有限的。所以,首先是明确我们要做什么,然后制定好一个合理的 目标 ,再一点一点将要学习的内容逐步付诸实践才是最重要的。
    3 \3 B( \- k7 ~3 \3 o, ^  每日一篇C语言打卡,目前更新到:光天化日学C语言(20)- 赋值运算符与赋值表达式 | 让代码变得更加简介(建议收藏)。6 F( k, j3 d, T! |+ {( G$ t; [* F1 q

    7 W* v2 t7 L& k  G$ r! V

    ! j5 q! F9 N$ f+ C' L& @8 h, C0 S1 |% v! d" S% w
    " B; J7 }" a- M' a. y! u8 K
    4 H1 r; U7 _. o# ?* f8 p2 F  B5 J

    8 a2 Z7 Y  i( E* @% V2 K1 }3 Z* K( {3 U1 V# _# T7 A+ E  P

    1 D( I: b7 ~# G5 p$ t. X/ J5 n图片较大,文章中有拆解,需要原图可以留言找我要哈6 d1 ^' g5 `4 X0 R" J
    1、基础语法学习. [$ G: ]% O! j2 A; N/ t
    算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。! w: Z- D! t4 o" {- I( x5 W8 F
    因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。5 W: {  [4 L$ u- o2 W
    0 I$ a) Z& c' k$ x; f& s5 y
    9 c' J6 |% G! r% J$ Z! F2 @
    ; f+ i) Y+ T" `! l. i- d+ U' d
    9 V1 q) D& a% c* ~
    1)HelloWorld+ F# k: W4 N) V  m
    无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。
    9 d0 e2 i; |' ?4 _# p2)让自己产生兴趣3 r; g0 `8 S6 |- `; z, I
    所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。' X7 y2 }3 B0 m3 r" G% }
    刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。
    8 T: D- R4 T9 {; @$ M3)目录是精髓5 d6 `4 i0 N1 Z' p
    然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:
    ( V% W. I- H% |7 \$ f1、第一个C语言程序9 }  F8 ?: g( L  u4 P
    2、搭建本地环境. I4 _6 Z6 U7 E
    3、变量, X; N4 t; x# E* F2 `& L# S
    4、标准输出$ x9 W- i0 l0 W; l' }* s0 W
    5、标准输入
    . Q5 H; m3 c. O. o; c! L4 \6、进制转换入门
    " |4 O) E* u3 g/ i7、ASCII字符
    ! e4 _& k8 j0 l+ A7 j! `8、常量" f8 }: R- @* `1 R% E6 _7 r
    5 A! }0 ?' w) |- P. r2 z7 S
    , [1 \: I9 q! z9 Y% B  |& a/ [
    如果你觉得这些名词中有 3 / 4 以上是没有什么概念的。那么,可能需要补齐一些数学、计算机方面的基础知识。反之,我们就可以继续下一步了。
    0 Q! b: H3 X3 B% k* O4 ~! [4)习惯思考并爱上它8 ~1 h/ E, F) ]9 T# h0 E; ~
    只要对一件事情养成习惯以后,你就会发现,再难的事情,都只是一点一点积累的过程。重要的是,每天学习的过程一定要吃透,养成主动思考的好习惯。因为,越到后面肯定是越难的,如果前期不养成习惯,后面很可能心有余而力不足。
    $ {5 o1 u; [* T! L1 S; J/ a就像刷题,一旦不会做就去找解题报告,最后就养成了看解题报告才会做题的习惯。当然这也是一种习惯,只不过不是一种好习惯罢了。9 n- b, q. `  h4 Q: M
    5)实践是检验真理的唯一标准
    4 r: ^" R0 c* {8 p" g& X0 m* \* m光看教程肯定是不行的,写代码肯定还是要动手的,因为有些语法你看一遍,必定忘记。但是写了几遍,永世难忘。这或许就是写代码的魅力所在吧。! J8 \4 E2 D- ?0 ^5 r- h, N
    所以,记得多写代码实践哟 (^U^)ノ~YO9 c# ^' O# [, f. K
    6)坚持其实并没有那么难
    % i$ C& R) N$ @8 z8 l/ x# L每天把教程上的内容,自己在键盘上敲一遍,坚持一天,两天,三天。你会发现,第四天就变成了习惯。所以坚持就是今天做了这件事情,明天继续做。$ l4 J% H. y3 M3 P
    7)适当给予正反馈0 C9 N# k  M2 A
    然而,就算再有趣的教程,看多了都会乏味,这是人性决定的,你我都逃不了。能够让你坚持下去的只有你自己,这时候,适当给予自己一些正反馈就显得尤为重要。比如,可以用一张表格将自己的学习计划记录下来,然后每天都去分析一下自己的数据。
    ) R( e6 D/ }$ i8 h当然,你也可以和我一样,创建一个博客,然后每天更新博文,就算没有内容,也坚持日更,久而久之,你会发现,下笔如有神,键盘任我行!更新的内容,可以是自己的学习笔记,心路历程 等等。2 z5 B6 K0 k2 n- C
    看着每天的粉丝量呈指数级增长,这是全网对你的认可,应该没有什么会是比这个更好的正反馈了。
    " E  X3 Q* }# Y% U8)学习需要有仪式感
      ^) S8 w9 X# q. _! n那么,至此,不知道屏幕前的你感想如何,反正正在打字的我已经激情澎湃了。已经全然忘记这一章是要讲C语言基础的了!
    ! E% u$ I3 z8 D) M5 O  n4 p* f) P6 w介于篇幅,我会把C语言基础的内容,放在这个专栏 《光天化日学C语言》 里面去讲,一天更新一篇,对啊,既然说了要坚持,要养成习惯,我当然也要做到啦~如果你学到了哪一章,可以在评论区评论 “打卡” ,也算是一种全网见证嘛!  ]. J" C$ ]( e
    我也很希望大家的学习速度能够超越我的更新速度。4 a( H  a* g! D$ C
    2、语法配套练习) B& g3 q/ ?# }3 g& |8 h
    学习的过程中,做题当然也是免不了的,还是应征那句话:实践是检验真理的唯一标准。% z. n4 K6 O7 K1 k" S. R
    而这里的题库,是我花了大量时间,搜罗了网上各大C语言教程里的例题,总结出来的思维导图,可以先大致看一眼:
    : B# F7 [. Z- q" Q) F/ F8 ]( Z7 `

    + b* c& c$ Y, W$ A$ D& Q. R& ]( V) t
    8 P9 q: V* ?+ \: {) U, B% l3 h
    从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。
      ~; \: p1 U/ M% M这里可以列举几个例子:) p5 ~) w7 O5 j( g
    1、例题1:交换变量的值$ f7 L* }$ G/ W
    一、题目描述7 A9 {- R# `; @8 f+ B
      循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。
    1 A, x: ~( n. x* d8 z0 X6 d# l0 S' z9 |' Y9 _
      M/ i) d& T& o& r3 U
    * D3 B( u$ f0 n! q( P$ T
    7 o: P: v7 R2 t5 b: q$ r5 J9 P. b
    二、解题思路4 Q/ K* e* F, `; G  B5 b: u
    难度:🔴⚪⚪⚪⚪) Y9 Q/ G- v5 F/ M  R; s

    : f% ]7 x+ X) h# I0 G
    ! [$ J5 |3 m; G! c( V1 C5 |% x3 n
    这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。% P% x. ~4 G4 L1 Q# D
    a, b = b, a  h$ U. a5 b5 }% k" ~. r1 L
    15 d  b) [: ^* `& t5 B
    在C语言里,这个语法是错误的。
    ( ^  _0 D% n7 s/ I我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?
    . Z1 E" J& v* l/ y当然是再找来一个临时杯子:
    ; v8 r! p) |# B, A& \3 Z! Z  1)先把 a aa 杯子的水倒进这个临时的杯子里;, X$ I" v" t* U7 l3 \
      2)再把 b bb 杯子的水倒进 a aa 杯子里;! O% I3 f$ G8 |/ B$ J
      3)最后把临时杯子里的水倒进 b bb 杯子;8 g& B. _/ L: m1 }/ F' R& E' N
    5 G( I# k3 f' a2 R+ ]
    6 ^7 m: @8 X0 f) G
    这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。
    2 B& Y7 N6 E" y& [9 l
    . G/ `5 z4 V5 `( i
    ( l9 M* z  [, A; y! M
    三、代码详解* G7 n: o" C, \" b7 P8 K6 |
    1、正确解法1:引入临时变量
    7 s+ @/ f+ b( u/ s+ ^#include <stdio.h>
    : t4 b+ K* w3 t; N. nint main() {7 c9 W" U( {' A: P" W, W
        int a, b, tmp;
    * R0 L; u, ~! l1 W$ B  d5 R( m9 f9 [        while (scanf("%d %d", &a, &b) != EOF) {
    ) f$ O7 @) n5 M4 ?" B            tmp = a;   // (1)
    ! K: |# T  r/ |& G! _1 j2 v            a = b;     // (2)- m- K3 b! A4 M/ V
                b = tmp;   // (3)( w8 ~+ k2 I9 b& L- S. F( S6 z
                printf("%d %d\n", a, b);
    1 N& N! D$ @) u# T- k; e# r        }( X9 p6 e/ Q( j; O, v4 J
            return 0;
    , E2 W6 h6 O' k6 c4 [! L; x}
    4 |$ D5 c" Z( k, O; ?. C15 n1 m0 T. L0 X
    22 }0 R3 E! J- c' U6 ?2 m. v* C
    3' M8 t0 [" Q( t8 H, p5 V4 W
    4
    ) c1 e5 c5 S/ l8 a, ~' ~2 J8 W5
    " ^  `2 q7 W/ ^# g- C0 F- F& I6+ H& A( v& C; O
    7& {1 N3 I$ t# c6 B
    8+ J! o% t# w, X
    92 d! \; `3 J/ H3 Q
    10
    . u" l8 b/ j* K( n. W: i11
    ( J+ E' Y' j4 v1 j" D( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;1 {- e) u2 c2 Y9 R
    ( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;
    0 q! v3 w" U$ m  X% }) X# m. F/ g6 v( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;
    / j1 s- x3 K2 {* c# E  m( A这三步,就实现了变量 a aa 和 b bb 的交换。
    ( e6 m+ [" O. A6 L  _2 T2、正确解法2:引入算术运算
    + d: W- y  Z6 D. r5 Q#include <stdio.h>
    9 e% `& z' z% c6 t/ M  x- A0 k  vint main() {. E6 C/ o  i+ I- I; f- c1 C2 P
        int a, b;8 |4 e) z. q' g8 m' Z: d
            while (scanf("%d %d", &a, &b) != EOF) {9 C% C, J/ ?8 K& X! S9 G5 ~
                a = a + b;   // (1)$ r4 l2 T1 p" p8 R" K0 i
                b = a - b;   // (2)+ R& J  q0 q9 ?: h( z& T
                a = a - b;   // (3)/ u* u, f& f6 u  n2 m# N2 X/ H
                printf("%d %d\n", a, b);
    $ l3 e* ~& w9 Y0 i; d        }
    & I$ M# `0 h# R4 k4 W        return 0;
    1 c2 r/ s* w( y) D: Y3 d, L}
    " ]! U; }1 y7 O7 U0 W7 X& `4 d1) C; q/ P. ~4 T6 O/ z
    2+ r! P; V0 l! v, f, r* R
    3
    7 W$ T2 E/ T) ?4 }. K; B4
    ' _2 I' N5 V2 K4 n; p1 F& A5
    6 k6 t( z' h4 n; c62 m1 M1 }! F- r0 _) O6 c. m
    7* ~( h! b' _7 q6 b
    8
    2 D8 V, p2 i( L8 g8 {9+ h2 m& v) k) P+ x4 k6 T9 Y+ q" p
    10
    5 o2 ~: D0 g8 R$ E0 V4 c0 W( L) s11
    , p8 i1 l- X6 `( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;
    + J. Q# s$ a4 D3 P0 C( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;3 Q( E% [# R6 q0 ?2 [
    ( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;
    , I! W9 L1 w5 y+ y  G! B( Q9 p; D# ^从而实现了变量a和b的交换。
    % x, B# s+ }: c; u3、正确解法3:引入异或运算. c- k. g* v2 I2 ^
    首先,介绍一下C语言中的^符号,代表的是异或。
    # u2 q' h; q! E! D# F' q二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:% b) T3 S" e" a+ y  O2 D
    左操作数        右操作数        异或结果
    6 d2 t5 P* b5 W( i( X/ m0        0        0
    $ @' I* I1 r1 K  Q  A! u( A1 R" @1        1        0
    # [9 h0 ]$ C8 h0        1        1: Q1 L0 Z" Q8 q$ ^
    1        0        13 H7 h' P3 v7 l) c7 Y) x
    也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。& g$ i/ A" t, q( s. z
    这样就有了三个比较清晰的性质:
      X$ t# p1 `2 K( B: S, D7 J3 [1)两个相同的十进制数异或的结果一定位零。
    - o0 v. F7 Z! w, n% R# m/ H' a2)任何一个数和 0 的异或结果一定是它本身。
    * `3 Q; ?8 M$ E- m! x3)异或运算满足结合律和交换律。
    , k9 [) p9 O; m% O  s+ T- B#include <stdio.h>
    / W3 a* S$ `+ k" mint main() {
    * Q9 ]6 r' V. W    int a, b;
    9 b2 V0 J3 g, u$ P  z' e6 M! Q        while (scanf("%d %d", &a, &b) != EOF) {( y& [" ]0 b8 B
                a = a ^ b;   // (1)
    : c& X* P4 f! e! g# x' U            b = a ^ b;   // (2)
    2 D% d: D# V! K2 W. A8 R, e            a = a ^ b;   // (3)* Y0 p0 V$ X: q/ d- a9 }" R
                printf("%d %d\n", a, b);
    8 ~3 g- e0 ], }        }
    ) {% E4 ]8 Q, j        return 0;4 ]; K" n; ?% P' a/ B3 T& J. `
    }
    , }4 v6 p5 I) r1
    * P6 U% J3 F" |. w' z% T2
    4 E+ I$ F4 I" w, O: C: P1 P: q3, Y* T- H  E5 e, _
    4$ }+ H$ z1 [" y6 R8 z
    5" s' I- H: h) ]- m1 [6 M$ Y& J
    6
    0 k% L1 H  d6 C2 m, G% G4 G7
    6 Y) i! [. B/ h. F7 @+ q+ Z& L2 W8
    7 G2 {7 F2 W/ H' B% s9
    8 t9 i5 m/ Q, d+ m' [) ^3 v# R) N10
    8 L+ X/ o0 c2 K% {0 ]. Q11; g' Z6 R: `4 B/ I, ]4 G, p3 |
    我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。+ Y5 n4 b4 J4 y) k  Y
    而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。' ]) l! o9 D" @, x5 e! B+ Z0 T# a
    从而实现了变量a和b的交换。4 U! |$ M" ?3 y! r! Y/ X

    ( C# e( Q+ R: `3 Q+ b! ]/ j, o

    4 z' L; N9 d9 g. s% k3 h4、正确解法4:奇淫技巧
    : b" `% e2 T% f3 ?1 u" `& f当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:4 G' V; ^1 U7 ]% A7 h, ^' R
    #include <stdio.h>+ D& u. x' K6 H- T, c# k' y
    int main() {
    / w* y1 g3 _3 K- ~) Z" Y" y2 h# S    int a, b;
    2 x* Z  \# D9 N9 P        while (scanf("%d %d", &a, &b) != EOF) {
    & F( K. s4 G* o' i1 l( @            printf("%d %d\n", b, a);
    . Q8 W  W' S( a5 B2 V        }
    0 ~) V4 y# Q$ d$ ]4 x: ~+ I        return 0;" ?  p- @( h7 d7 }  A/ _
    }; Z/ H7 @( T) B" p7 H
    1! C! {8 k1 H! u9 o: N3 f. d. P
    2: A! y) U2 P& o! ^. N8 v
    3, N5 v6 C5 J4 {, q7 P( m
    4
    1 ^9 s# z% B$ X. j1 ^5$ ?  P& F" R' |: s4 _! z
    6
    0 }2 L: g; ]3 B( y7
    8 e5 {4 P, n# ]* S+ W+ u* a) J8
    9 d6 K8 Q3 N' k0 J1 E3 e% q$ d你学废了吗 &#129315;?
    & c$ U- O, `1 R2、例题2:整数溢出0 k7 ^, @- x* U. S/ B
    一、题目描述+ ?9 M$ E2 F/ `2 [9 U. x" `
      先输入一个 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
    , @; r$ t3 U) A3 M$ Y62# }7 n/ Q  Y3 ]
    ),输出 a + b + c + d a+b+c+da+b+c+d 的值。9 z1 c5 v# t# e4 H$ R1 ~. ?! v

    % U* G/ A/ x7 L) Z, w6 _2 E

    $ z2 u& Y$ Z& }8 s% ~5 m( {' [: I二、解题思路/ p1 L4 n, A$ F  U) L2 z$ _7 D
    难度:&#128308;&#128308;⚪⚪⚪
    0 ?# K& m2 d( A% y
    ; x/ j1 q8 ]6 }; |4 z6 Y; t0 q. X

    # Z* V* {6 Y) O! ]& n这个问题考察的是对补码的理解。
    3 b! w8 B; i( M/ z* J仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2 ) w/ g5 g2 d. P; }7 k! K
    62
    & w+ x5 G) A, C5 X& [ ],这四个数加起来的和最大值为 2 64 2^{64}2 ' Y# W; ^8 g) R. C
    64+ K  T% T$ n5 C& ?% u  \- Z  y/ p
    。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12
    0 o" ]$ Y/ _! b1 C6 c; Q  t! j4 M63
    0 `" i: f# J: C7 T# x4 S3 n −1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12 9 R- |! K" Y% x: U8 w* b: p
    64
    1 L/ a1 Z. t4 x6 T: K −1。
    , Y/ Y  k+ _. e" W5 C- r* o# Q' T: G但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2
    4 g3 T" a% g9 F! {( s) X624 t$ P+ ?* b1 a! x( L
      时,结果才为 2 64 2^{64}2
    $ J: J7 @" Q- j; Q$ z( \3 d649 n: C6 Z1 ?# E" `' q' `2 q
    ,所以可以对这一种情况进行特殊判断,具体参考代码详解。
    & [( E$ R, @2 M三、代码详解
    $ [& ?$ G2 F% K3 x, o( ?: k#include <stdio.h>
      i2 A% G* @& v/ j. Y3 V! R% V* ttypedef unsigned long long ull;                           // (1)9 h7 U# _: d3 T" f
    const ull MAX = (((ull)1)<<62);                           // (2)4 J+ {. Y* |# l0 {6 E* e

    8 D' f: ~& Y* D* e  j

    . ^' ?! R5 h$ d1 f$ vint main() {- n3 V4 @/ K% s1 P5 H, M
            int t;2 M! f7 V2 z* v% w
            ull a, b, c, d;7 U" ~' Q2 m  L7 N/ M4 S$ Y
            scanf("%d", &t);
    0 w" }2 ~2 c. b% R/ _4 H3 v$ s3 H  ~8 X        while (t--) {0 t( w1 H) q- F6 f" v
                    scanf("%llu %llu %llu %llu", &a, &b, &c, &d);     // (3)) d! e; `7 h% K' V" A
                    if (a == MAX && b == MAX && c == MAX && d == MAX) // (4)/ A4 \, m/ w; `. J" n  B
                            printf("18446744073709551616\n");             // (5)
    # c! F( @' d& W" @. c- Z                else* q7 b4 w# s! E: v8 E
                            printf("%llu\n", a + b + c + d);              // (6)
    9 C! m1 Y% g. \! ^7 F' }3 J        }: T8 _6 E0 e1 M( ?
            return 0;
    9 X, ?/ o. R8 @* ~2 F1 V( P}5 t) ?: u9 M3 U( W
    1( Q/ S" U' s8 u& ]8 l
    2
    ) Q9 z3 C: h4 G% K3
    ( O7 L9 {; i) n$ f4
    " L! h# Y* {& |5 c1 |' Q) R5! _# a; P! M& K' D- c& B. q  J6 ]
    6& M1 k" z9 j( m' a, q; L% Y
    7/ }/ N* M5 x; Q6 M% l3 G6 p9 c( B- T
    8
    7 j/ Y# f% G! F( S9! x4 n* g: b2 D4 Z4 n$ M
    10; N9 ^; W8 O/ P; I# Y9 U8 B! x1 A
    11
    . ^( w( P. \6 L% H6 c12
    2 l; g" g5 h2 y$ a139 R" f- _: t  {5 x$ l) }
    14
    $ C3 W5 O% m+ ^/ M: X15
    % x1 i) y$ Z$ y% X16
    ! r4 r; A1 |: |  Q) E17; x1 y* ^+ s" [& V  T
    ( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;
    4 W% t4 V1 l% T4 y. c# p( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2
    : T7 L0 L, S% T' E) u' h6 |620 }" L8 k& t: k5 n7 i/ E; K$ ?3 [0 N/ @
    ,这里采用左移运算符直接实现 2 22 是幂运算;* b) L: B7 k$ \+ r4 X4 `
    数学        C语言
    , u3 X: B- @" K# i2 n 2^n2
    8 C+ }+ P* T' a/ V8 B0 _8 h: mn
    7 a; f% F: I! }7 }- k/ T         1<<n" K( g2 a1 n) |! T" A5 j
    需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;
    ) y$ P2 [8 b6 f4 X1 s7 F5 ]( 3 ) (3)(3) %llu是无符号64位整型的输入方式;+ e) |. i: g9 A: C1 h* Z
    ( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;$ Y- R5 o' }& ?% f. D; Z- [% e
    ( 5 ) (5)(5) 由于 2 64 2^{64}2 " `1 L$ V% a( t: M4 O! j2 [
    64
    0 i) ~. N9 E/ @  是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;
    9 z$ b2 r  k5 A+ u. X: f1 c6 Z( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2
    5 N+ D3 a7 R2 |1 K' ]/ Y643 c! u) c1 I) @+ v( ]" s$ Z
    −1] 范围内,直接相加输出即可。
    ' g, _& E, i  a2 A由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。
    9 N$ I/ q6 R; ?; L# s为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 &#129315;。
    8 [0 }- t2 t% P! R. T4 m' w
    9 _6 R3 O( ]  z3 g' C" ~% Y

    : T8 q  [; r8 i+ ^: h, ]3、数据结构
    3 f+ K1 Y, p6 c# m9 B《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。" B( ^$ C) v" l, H
    1、什么是数据结构5 c7 T) D  l5 Q% p( ^: G
    你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。
    " z6 b" \4 L$ \- E如果一定要给出一个官方的解释,那么它就是:
    % L" Y! P9 ^9 y计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。
    . B4 _8 h5 z& E% t! @9 c8 L7 `2 p: ^' z# v7 C
    % {0 m' X6 K: S# e
    是不是还不如说它是堆,是栈,是队列呢?
    : E# r( i+ f, N0 m3 u, v是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。9 S, D2 p! [( o
    2、数据结构和算法的关系' j* Q* x+ ~$ v) Q# m* d' ~1 _
    很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。/ J9 u$ u" q. Z4 ^. u% z' |$ ^
    数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。
    : M  h* H* q; o0 s/ A而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?6 ?! ~2 g2 S" Y: z
    讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。* j9 Z0 F7 D& `" a8 f, g9 }1 C" U
    3、数据结构概览: b3 L& k" O5 ?% t  c
    周末花了一个下午整理的思维导图,数据结构:
    $ w2 y' H! o( Y9 H5 e4 {: ^4 v- V$ x2 G9 W. {* U

    & q! h2 E) ]5 M, G0 k常用的一些数据结构,各自有各自的优缺点,总结如下:
    ; @6 e1 I5 C" v) C7 _' h- v2 b) J* ma、数组4 C5 b- L( z& c  q$ {4 I8 {2 I
    内存结构:内存空间连续" n0 s: ~9 a8 P3 u6 E5 U8 \' ?
    实现难度:简单! s5 c6 w( O8 Y+ \
    下标访问:支持8 t- x: q7 b+ _1 S) ~4 k
    分类:静态数组、动态数组5 P5 ?# s2 Q( H/ M& O0 ^+ _# ?; r
    插入时间复杂度:O ( n ) O(n)O(n)! A6 P6 z0 @+ L" A; e
    查找时间复杂度:O ( n ) O(n)O(n): E. B: J4 d+ ?
    删除时间复杂度:O ( n ) O(n)O(n)
    ! W  N  l) X! K# y7 i2 J( n# q! h' c* F  Z) ^! s$ Z7 y

    0 R0 ~/ }1 Z+ |+ e  n' ob、字符串
    ) r4 y1 k2 I. {6 J* L内存结构:内存空间连续,类似字符数组
    4 B' F) p; O; ^, z实现难度:简单,一般系统会提供一些方便的字符串操作函数
    ' j/ ]/ x) z2 W# v* L下标访问:支持
    : h0 n0 h6 [+ w  K插入时间复杂度:O ( n ) O(n)O(n)3 T7 r* x% C, j4 m
    查找时间复杂度:O ( n ) O(n)O(n)
      D  h5 \% }, {$ Q删除时间复杂度:O ( n ) O(n)O(n)
    & x8 j6 f9 w# a2 k# F
    9 \: d) p: }7 j( d. n8 ]9 g: {) j
      Q( w4 ]! a3 z  C: x& D
    c、链表
    " [* M4 q5 ]/ ]- q- s6 E内存结构:内存空间连续不连续,看具体实现
    ' V4 n- I7 _& j& R( X0 v实现难度:一般/ L7 @% t& ^! y3 P) S
    下标访问:不支持
    0 {% T4 p3 F3 |* p) b9 l/ V1 M4 l分类:单向链表、双向链表、循环链表、DancingLinks
    4 d/ q, J" }, j' b/ h9 i插入时间复杂度:O ( 1 ) O(1)O(1). p8 T/ v6 [0 |3 l3 b0 g, H
    查找时间复杂度:O ( n ) O(n)O(n)9 F- x- F$ f9 n& }; m3 n
    删除时间复杂度:O ( 1 ) O(1)O(1)1 C0 T: }$ |, ^' b, E* W

    ) B' n" r: g: |$ n  g0 l$ y4 `

    / c( w$ Q7 C0 }, D6 S3 xd、哈希表
    $ R9 u8 B. O% I6 ?7 F( }内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续7 K5 q) J7 X6 v( p6 a3 }1 z
    实现难度:一般' e2 ^# J0 ~4 i+ n& C8 F
    下标访问:不支持3 H  G0 q, @9 I1 \3 o
    分类:正数哈希、字符串哈希、滚动哈希+ g. i9 y2 b/ v0 j0 c
    插入时间复杂度:O ( 1 ) O(1)O(1)
    8 g+ p6 U/ Y& M查找时间复杂度:O ( 1 ) O(1)O(1)
    9 g6 K: p& U: U$ w& J5 q删除时间复杂度:O ( 1 ) O(1)O(1), D9 }8 g. G% i% T( q
    ( [4 n% u9 k" o9 d: I  y: C
      k* Z3 E0 t# O7 }4 m) Q, \% |
    e、队列
    $ k0 ?+ [2 h- R内存结构:看用数组实现,还是链表实现
    5 N/ t, N9 R+ I: X7 b实现难度:一般
    + ]) R3 ^# q1 k( g下标访问:不支持
    ; K; X* A' ]* |& N分类:FIFO、单调队列、双端队列
    + ]) C9 _& O4 i$ `+ W插入时间复杂度:O ( 1 ) O(1)O(1)
    # x4 ~8 d+ J' l. L$ c查找时间复杂度:理论上不支持6 z$ c; b6 i6 D* p1 N6 b; M1 _+ d
    删除时间复杂度:O ( 1 ) O(1)O(1)
    , r! m6 R/ k* B6 S7 r" B
    5 _! f7 Y% m- u' ]& l

    . D* q$ x( ]' `" ]6 l$ Jf、栈
    % H3 X8 o' v( k! r# K内存结构:看用数组实现,还是链表实现' p$ e5 f4 k- m: Q0 y' i( N. Z
    实现难度:一般
    ! x; Y4 f/ ]! |3 X下标访问:不支持
    9 W( h. A# J/ {" A分类:FILO、单调栈1 B) y8 B* g! }( ~& _9 c
    插入时间复杂度:O ( 1 ) O(1)O(1)
    0 x, K" B% \/ X5 w" p查找时间复杂度:理论上不支持
    ) [+ q8 @  W) u' _- s+ E( L* i删除时间复杂度:O ( 1 ) O(1)O(1)0 d' |7 j8 J7 {/ k  A  Z, Q5 D
    . P, s5 `8 ]; Y1 b

    9 J# a& R0 n& |4 k# ^g、树
    ) I5 Q! L1 M& f; s内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续4 h: ^+ w/ {' L1 n7 Q/ V( `
    实现难度:较难
    * u4 x* q' z$ h3 @; m下标访问:不支持
    5 o' {* Z) D7 c# {) K$ t* p, J; i分类:二叉树 和 多叉树/ d  P; _* t* w/ c
    插入时间复杂度:看情况而定7 `! G' J- `" O, `8 m2 t
    查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log 2 _7 ^( ~8 R% g, b4 y
    2
    4 C* V' h1 Z" ~7 J  A3 J, M* Q) s+ y2 z​       
    7 \, V% {+ }2 U n)4 `& a! j* |3 [4 Q: P3 L! I; I) i
    删除时间复杂度:看情况而定2 t; I( W  N+ H/ v% e
      f. y" D5 U- n& a3 O
    * _0 ?5 s% m. V7 c6 _, e" L) C2 \" J" x
    1、二叉树" m7 F7 |& n) c6 Z# d6 a* x
    二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。1 x0 t- S4 {& k$ ]1 F+ ]* H6 R
    其中,堆也是一种二叉树,也就是我们常说的优先队列。
    5 r* u+ a8 W6 X& c+ r, ]+ Z2、多叉树
    0 [2 \/ R2 E$ v$ {2 @B树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。
    2 d1 c: S$ O1 ~6 |6 y. ih、图
    9 z4 E0 ]! L# S- Y& G内存结构:不一定. L$ q& }' Y4 ~4 E$ Y. B
    实现难度:难
    8 G! M, ~2 y9 [下标访问:不支持
    , s* R$ I9 E- R0 @+ R分类:有向图、无向图
    ! T, e. r% }: J0 g2 T9 C插入时间复杂度:根据算法而定9 Y; G& {+ Y, Q+ ?- M
    查找时间复杂度:根据算法而定/ z; a" g: \% s; n; \" E' a. P% g
    删除时间复杂度:根据算法而定
    ; S, }, H' H; [( O( p1 P6 U# d# G/ C" c; p' P
    6 {# W- l; `  u. e8 n
    1、图的概念
    1 W# p9 p- p# L7 R  V在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:
    * ~& ]! z2 J& p1 D- l: H( Y' o图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。4 [% j1 l3 f8 s3 |& y
    对于无权图,边由二元组 ( 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 为权值,可以是任意类型。
    8 r# C+ F9 E+ T; ~+ `) y图分为有向图和无向图,对于有向图, ( 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;
    2 L. ^  S7 k1 S- I2、图的存储* ~6 {; y0 R! l' T: H
    对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。  Y! o/ U5 q. d4 y1 @7 t

    0 J: ~$ R& z4 r9 Q/ ~1 @
    7 U/ c  N6 i% A; e+ r6 O' B
    1)邻接矩阵
    # c$ f' m' H" u邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。
    9 b: {5 J4 U, G. k7 o* C它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。  t$ r+ c& v* k/ o$ B  J4 S5 t
    [ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[$ `  x2 g1 R5 \) S, M0 ?2 H" P$ r& m
    01∞9∞0∞8320∞∞∞30
    ; l* N# E/ N& u% L# `$ n0∞3∞102∞∞∞0398∞0, z' m8 h6 n' x8 g9 j
    \right]
    5 z- t* V; {6 T+ \
    ( j" V9 Y: t, n. R& H! r; d/ W+ J* R/ ~

    2 I; M& H9 W2 X; d) {$ ]+ e! ^& U' w3 C
    ​        3 ?3 Y, G. A4 u9 C) `
      
    , p2 {- P3 T2 e7 m# B- b+ c1 e0
    9 C+ |/ \% B9 c1- }, I9 ^3 \9 \$ ^
    " s" u+ }' `- Y  [# D' ^
    9
    ! F0 }9 J, i8 s1 N5 T6 {* f; A​        2 D" c8 c6 q: `  G
      
    - B" F' _: X2 `6 o' n7 C8 K) I  j7 N) k
    3 _% B. a4 F* x) Z6 R; ?$ w0; `) Q; x( q4 M& d3 y6 P; z5 _, ~3 f
    6 t- v$ K( }4 G
    8
    & w6 m2 p1 i5 m& j8 }& [& }​       
    - K! A( Q' Y- a" w2 u! D- P  
    - V8 C' t. n: Y5 j: j" M34 t, m* j# Q& R, o- W0 s+ X$ z" T
    2) U3 B3 U% ]# S# H
    06 s5 ^' Q+ h$ Y
    ) r. k( l% A$ x- \" X
    ​        ) ~3 J) `2 n7 f& T% z
      % c: ]1 ]5 S* s4 G
    ! B4 m/ _% J6 y* f' N# W

    8 H* d: t6 F3 O+ Z9 s- m$ P9 T38 j8 l$ a7 Z2 j$ o
    0% n' {6 T; z- g" D9 w/ }/ {. v, F
    ​        % ^2 v& z$ a$ x6 t1 e9 T5 G
      
    ' }. n5 [- k7 Y2 X. H9 Y  \) a( [& [+ E* r: B% }( D
    - _" _2 ~# T5 u  a. _
    7 G0 l9 X2 g' N
    & N+ ?! w9 z; M& ?
    ​       
    $ t5 n2 Z6 B0 ^. ~6 k
    ; k/ `0 x/ x2 \9 l. K# m7 T( ~) [2)邻接表
    5 b* R3 l( U1 w0 s) W1 P邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。
    0 B1 ^/ y( s- Z# c它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。7 }  r9 B5 m4 _( ^* F/ e, n, u
    如图所示,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) 二元组。, C* D3 W/ h. t  P5 q! T: u. P3 Z7 [

    4 e0 ^9 ^5 Z5 e9 L

    # E0 Z2 C: f5 N! m* ?在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;. @* g# \! s$ H
        vector<Edge> edges[maxn];
    7 ^$ K  l4 P8 [. @, B0 M19 G5 ]. W  `9 M0 G
    3)前向星
    $ W$ O* i" D/ v# b前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。
    " W. U, g6 ?5 U% c它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。% P9 `! [3 w$ A7 w
    如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。
    " f: e! d) s, m6 _' e, W: c& _- F- u8 t. N: L+ m$ `' ]% K0 O5 R
    ) [) O! Y2 X8 O
    那么用哪种数据结构才能满足所有图的需求呢?
    : }/ O; X, o- X5 ^  ^接下来介绍一种新的数据结构 —— 链式前向星。
    7 O8 X$ {/ _# k$ ?1 w: ]( G4)链式前向星
    # a# V: H3 Q" L8 I: K链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 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 指向下一条边。- _) C/ W* A( G' t
    具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。
    2 M% m. z5 `3 @, A8 ~( i* p边的结构体声明如下:
    * Y' l/ V/ l6 bstruct Edge {
    # n; X' v9 K8 Y( [8 K0 Q& N    int u, v, w, next;
    - l8 J3 J/ D6 ]0 b0 w    Edge() {}4 S, Q$ H" O# j0 u& b0 H
        Edge(int _u, int _v, int _w, int _next) :
    6 Z4 U' \9 H' X* k        u(_u), v(_v), w(_w), next(_next) $ t! S- l6 J4 @( L; a9 ]$ C( b
        {3 h2 y+ u  v+ \1 X
        }
    ' ~1 }3 W8 Z2 m8 J" e& z}edge[maxm];+ |2 n- z: t6 W
    1
    2 E3 @( j6 S8 [5 f: _28 z% }% @5 y1 x- T0 ]
    3
    9 a8 e0 V6 |* l44 }( f1 Z( d9 C! c9 b
    5  X. k+ C& p, Q8 @1 h
    62 j! t! [, X$ C6 p
    78 ^8 d6 T  S$ B  D
    8
    - m, D9 U: d$ _. P" V# I, z初始化所有的head = -1,当前边总数 edgeCount = 0;0 I2 Y1 r2 a. y" P& ~# v
    每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:- s  F- Y5 D! ^: L
    void addEdge(int u, int v, int w) {$ E# U8 n& i- L, P+ r
        edge[edgeCount] = Edge(u, v, w, head);; i& {  V! E- t; G8 ^/ Z% Q
        head = edgeCount++;
    , \6 H* l5 ?3 u$ n/ W}
    1 S( D1 Z7 u1 U% Q! f1* R3 c+ ~# o; ~! P$ [: H- l
    2) q  z% G) m* w" p* H% ^* @9 f
    3
    # Y0 O; S, h" m7 Y. R  X) S' d) m# X4
    " @$ ~6 p0 m0 M) q) G这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。+ C- a5 ]: e& M5 }+ Z8 e
    调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。/ n5 V. H& k0 E% m, V5 l
    for (int e = head; ~e; e = edges[e].next) {
    ( z! G& x/ d3 c4 D( {0 z: h. ~    int v = edges[e].v;, g. ]( H4 }- z9 T9 `1 c: x& K
        ValueType w = edges[e].w;" V5 p7 p* }. ]" K
        ...' [0 @$ w0 q% w! }) i! h3 ^
    }
    + T! s6 o! g1 s6 P! F1
    + e( a; i2 w, h5 N& O) C9 H& K2
      _8 Q- x/ Q1 S- R  p) u3& u7 O. t7 q. s8 ~  R) F
    4' W, Z" C8 w% z4 P# Q% T
    5$ L$ A5 f  b8 S0 C0 i
    文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。. N+ ~2 W+ e6 x: w& B' B
    4、算法入门
    3 u+ d4 K' `: ^7 G* t4 h  ]算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。
    5 ^! h5 n/ s- V  G& Z' \0 l
    ' e8 A5 p7 L- E2 X9 T

    , g  W5 [$ e2 ~: ~% N9 L, d9 @5 n- Q入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。
    . N, N5 Q  c0 Z! G. @7 ?对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。( b* b4 Z- C7 z% s. M% {
    1、枚举
    : T% D& ?+ x, H) J1 P; I: j枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。
    $ ~* H& g- q# b对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。1 g; P+ M$ [7 f) V( a
    2、排序4 M+ a/ ~2 H; D+ R
    既然是入门,千万不要去看快排、希尔排序这种冷门排序。
    6 l5 c" {9 ?# E2 [" q! n* F冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。
    ( u# h+ x- f% V9 \% w5 {" [# CC中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。
    / h' x; P: K& j  j$ [2 \9 ?3、模拟2 Q( ?7 g) S4 u+ Z) ]
    模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。
    5 j4 L- g1 @8 V0 s3 G( G4 g+ }  B. G不管时间复杂度 和 空间复杂度,放手去做!
    0 \, b3 F# o  P) P& x5 y但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。
    * W: a  l* w- X% x. w4 i* Q4、二分' V9 W5 q+ s$ ~# M0 ?* K; k
    二分一般指二分查找,当然有时候也指代二分枚举。
    : I& |8 ^6 x6 `; Q+ m1 |例如,在一个有序数组中查找值,我们一般这个干:$ b9 }# s1 u2 u$ C1 N
    1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;8 ?2 }$ f7 e1 v) n
    2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:8 E& ^. z: G; Q) R5 C
      2.a)目标值 等于 数组元素,直接返回 m i d midmid;
    " ?0 d' d, [$ E5 x: X  2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;
    2 y+ e0 C- W( O' A  2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;
    * g+ k2 v4 b) S, U  Y" q/ o3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。
    0 Y! E3 d3 z! m6 ^5、双指针) Q3 R. ]6 j2 N* X6 ?, m
    双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。
    + k8 M! }* |( M9 T1 q% q0 m* T; @

    0 r% C1 |5 g0 p* f0 Z6、差分法+ I5 _" D9 j- ?9 n/ ^- l1 `
    差分法一般配合前缀和。
    9 x6 \9 x8 V& P* b" c& U; _对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;( g" c7 i  y9 M& j& _
    假设 [ 0 , x ] [0, x][0,x] 内的 g o o d   n u m b e r good \ numbergood number 数量为 g x g_xg # Y' k+ K' c! v) Z
    x0 Y) w: S5 G; ^
    ​        * `' H7 h' x' E" I& [
    ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g
    % N7 B7 L1 S, k, jr2 J! o% W* r" {; F# n
    ​        , `' T) m# {+ p# b& @
    −g - X. M: ?$ H. C
    l−1
    * `  }. Q& a  k( {, X​       
    1 B# |( k( j* j ;分别用同样的方法求出 g r g_rg
    ! b; C( ?  F  k7 Z) r6 p, @; [( Tr: g5 v7 ~) Y; h3 C+ @! M7 S9 J
    ​        : i; F' E- V1 U& [- {
      和 g l − 1 g_{l-1}g
    - v( b7 I1 p; j% s! Yl−1; u  b4 x7 Z' s; h, J
    ​        7 D- b! t. Q' h$ q# b7 `
    ,再相减即可;4 F' H3 M+ X$ w- |4 X
    8 P% |: g9 O$ l
    7 I: H4 Z  ^4 ]) O4 Z5 I) D
    7、位运算* p  ~' F' t& |! i6 }0 G" M/ G' b
    位运算可以理解成对二进制数字上的每一个位进行操作的运算。
    6 F- b+ t% Q, ], Q, L/ o位运算分为 布尔位运算符 和 移位位运算符。9 R" H/ D9 ?: `$ W3 `
    布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。( ~, u/ F. f3 @2 U
    如图所示:
    ! q( v9 y3 ^5 b6 {  n, v9 A; `0 u" b( ~  \0 w) A
    - O4 ~# Y1 n  Q7 `8 s( w1 F/ W6 O! x
    位运算的特点是语句短,但是可以干大事!3 T1 Y2 e( j3 K. k. s  V
    比如,请用一句话来判断一个数是否是2的幂,代码如下:
    5 J" R( G6 X) _& r!(x & (x - 1))
    1 V4 J7 ?' ~* a( M- t2 B1
    ! m, Y# ~# I' n+ t$ J1 W8 f1 z8、贪心# ~$ A2 H  Q9 p2 J5 }
    贪心,一般就是按照当前最优解,去推算全局最优解。: I# R3 I# e$ a  A
    所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。* F8 X! j; G. ~( u
    9、迭代5 v2 _* h) J7 O- d3 c
    每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。# {8 M  R3 I  r' U( s  q
    10、分治2 e+ p  ~6 V$ T1 [8 R; h
    分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。
    ! b8 o" t& h6 r7 t7 M5、算法进阶
    ; ^9 i/ q4 V- p/ O8 t9 o6 K算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。
    ' `% T  f" v, D. L- K- t( y如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。
    2 ~5 Q$ R0 n: P! m" _: A' C这个系列主要分为以下几个大块内容:
    # O! c' L/ M8 a9 S% D8 [  1)图论0 a5 E& ]" n/ E4 A1 `% e- w& z$ `
      2)动态规划
    5 @+ b" I2 |( J7 ~7 b  _+ |- Z5 ]  3)计算几何' d6 B1 X3 d; u3 o! H4 h1 B" n
      4)数论
    2 U: P) Y. I9 B& L6 V/ C0 |  5)字符串匹配% U; L( ~  v! T1 E/ S7 z! v
      6)高级数据结构(课本上学不到的)+ U* r' U8 Y- e) Q
      7)杂项算法
    ! ]( e: l9 V  l+ q' g/ Q- ^: ^4 m
    # R; v  @0 _5 f2 J, Z) W. n
    先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:1 d3 _2 g' o' S3 b

    & e) d, u6 ~0 L+ ~  N. x# Q3 f

    / W" |$ H+ }& `% W( Y* _( p0 L* }
    2 {- k/ Y) B- @+ G; X
    & M8 ^+ s6 R0 W/ |: l
    1)图论9 k3 C5 I- V+ u) B
    1、搜索概览
    , t5 A* g% _7 u  c图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。3 d1 f: E7 [) }) Q5 l1 G. H
    & U3 I& x2 K" a4 e/ }/ c7 ~
    9 z3 m6 j! _! M
    比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。0 ?5 U0 x. X8 S1 w( p
    2、深度优先搜索
    , _0 ^# k; f: D& j$ X# }, F. B3 a深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;
    . H$ S: X4 f3 J7 a* y原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。' A7 q1 R% d3 y
    但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。0 s! u4 B# A+ t
    如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。) W; a, H" u+ I7 y% L

    9 T/ A* R7 v  L0 O- o0 d

    ( z: r$ Q- j, \  p2 u: ?( [% j% G; A红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:" l9 ?4 d6 {2 _6 p6 ]5 U
            0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 6! E& k/ m& q' ?8 H3 c: g
    1
    4 Z* x4 q( a9 ~' I同样,搜索的例子还有:
    ( m; e& M# @4 s# {( _* F
    , Y) @( ^2 n+ i

      a2 {2 ^1 Y6 H) Y计算的是利用递归实现的 n nn 的阶乘。
    % \; L! Z+ u( F! W3、记忆化搜索
    6 o# `5 Z" s( [9 C' T对于斐波那契函数的求解,如下所示:( J. ^. i0 |' i3 `1 G& s1 i+ v: ?
    f ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =
    + u7 [8 ~- i3 |' c, k⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)
    8 F" u* t1 ~& ~{1(n=0)1(n=1)f(n−1)+f(n−2)(n>2)
    ) W8 j) }7 S. X2 G. W2 ?f(n)= , z4 i$ W% F# R( H

    # O6 G" m7 B- u# ]0 P+ ~6 P( D7 W8 S. A9 F! q

    ! B; E" Q. r" O$ y# B1 k
    9 O* B9 `# A* z/ S0 K- X3 T) l0 Y+ a
    ​        " Z0 E& _# |8 |6 p
      7 r; d/ Z  I8 T. X9 c
    11 Y7 X7 C: @: c: v) }0 y; \# ^
    1
    % A/ M- G( a4 T# M  df(n−1)+f(n−2)
    9 Q, E* ~; D& ^8 ?( I​        7 V, R& U6 ]& e0 a& v8 s& m" L
      
    " H. a1 W: H) J8 ?. H(n=0)
    ( x7 A9 R" u6 X0 ~(n=1)( e' L. V6 i: T5 T  J
    (n>2): {8 T' k5 M/ A. t3 `
    ​        ) I+ L) X. p9 }  f; F) K0 y. @" y

    . {2 P" |; B6 D6 D2 D6 h对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:6 P, \! ?, K+ f$ Y( l6 r4 U

    5 }8 ~1 Z+ t5 A: s+ I# F
      E* {7 }1 r6 n1 m0 }1 ?
    这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。
    8 A. N9 [5 s" R5 B我们通过一个动图来感受一下:+ d. B  I  z/ ~& `# M, N

    . _) u. Q( ^% \- g0 y
    : l! |8 Z% ~8 u9 P" S. A
    当第二次需要计算 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表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。
    7 Y- M* Y1 B+ \# O+ U  ^& F这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。
    + A! u0 Z, c% l, ~3 r% N4、广度优先搜索
    8 {9 h) a2 W$ b) R) n# D  }( e单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。
    3 e0 j4 Z, l0 @6 ^: S$ r我们通过一个动图来对广搜有一个初步的印象。: |" ]+ d+ u0 K' T1 j8 t  h# S

    1 l4 A; q# O9 p. N* n

    $ W; Z3 ~8 p# f; x
    6 x7 d: o, E2 s
    / U. u% i3 e" H$ K% {  g4 t0 j& v4 R
    从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。/ `4 N' _& Y# k5 W# I  I
    那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。6 B/ o" _; h0 F
    这时候,算法和数据结构就完美结合了。! u4 \+ l3 K) y4 U9 O
    2)动态规划* r& @/ r' h8 t' Q
    动态规划算法三要素:7 X+ L$ J- Y& J. P0 \3 X
      ①所有不同的子问题组成的表;; b7 h' L* d$ D; t, R
      ②解决问题的依赖关系可以看成是一个图;
    5 q" D8 q( E) Y8 ~3 N  ③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);  x+ V% n2 H; p
    0 q; L8 t( [- O- N- ?! H

    2 H) ^, @7 a1 J如果子问题的数目为 O ( n t ) O(n^t)O(n
    & Q  H5 K7 N5 m' P8 J5 l% a3 |t: Q2 c3 U9 ?! W9 H
    ),每个子问题需要用到 O ( n e ) O(n^e)O(n 6 z) c: [+ f7 D: h) `, |
    e
    : K+ D* y# \& _- L! O ) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。/ A! j* c* i' q
    1、1D/1D: [- y0 e8 r5 z; O
    d [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j ). I( j6 A+ X% p; X# p# r- T8 T
    d=opt(d[j]+w(j,i)∣0<=i<j)
    8 }3 J# t; a# R; m状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):4 a# w* N( m0 H1 e0 q
    5 L0 ~* G6 a% ]0 K/ \0 e: x  k

    8 G, F  z/ C, i( Z& o, V这类状态转移方程一般出现在线性模型中。
    " w9 ]( ]8 E, R+ Q. v. ?5 b- B' F/ P2、2D/0D
    2 }7 x. S3 s/ hd [ 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} )# G1 n% t( m, U7 k* N- ^
    d[j]=opt(d[i−1][j]+x 1 h2 W2 T: k6 D( Z2 _$ X
    i
    % v1 r7 d% j1 l- a' i​        ' H4 `8 }! s' }2 S. F# S
    ,d[j−1]+y + Y6 O6 V  X1 j1 c0 W
    j3 c  S/ D" L6 J- E  V
    ​       
    " g8 c6 }- h; P3 B; i! G+ w% s6 u ,d[i−1][j−1]+z
    " `' F9 i/ K5 X7 ?- C' @. Nij
    - ]& _/ y  a. O. I- s5 y​        * a4 J: b+ G  @
    )
    # Q% Q0 M+ \: J/ f, p0 [- m状态转移如图四所示:
    2 T- F/ U* `: @# R& a
    ! [, @2 x# R# r* d8 b. Y; w
    ; e# Z3 z! l0 ?. b
    比较经典的问题是最长公共子序列、最小编辑距离。
    $ Y7 O3 u! z$ U, e  `8 \# d有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列. D1 T5 R% b. b9 ^" g/ w0 |
    有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离
    : ]9 B; A1 l3 U' u  D1 p' K3、2D/1D/ b( L( ]- s! {. k* y7 a
    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] )3 R8 r* o1 y5 m5 z9 }. W9 S
    d[j]=w(i,j)+opt(d[k−1]+d[k][j])
    6 a. p! }! r" I' v0 f7 d区间模型常用方程,如图所示:5 t/ }5 Y: h% K) A& j7 Q: D

    1 U  n6 q- m; B4 X

    % _( i0 l0 z, W, x& S另外一种常用的 2D/1D 的方程为:/ p0 W2 X8 c7 g# h
    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 )5 Z6 S9 M9 M. B) [
    d[j]=opt(d[i−1][k]+w(i,j,k)∣k<j)
    ) ^& h9 [$ I+ e% X* h( z区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP; S5 \6 a+ ~1 x. n+ p3 h! s! T7 q
    4、2D/2D, ]. v% r8 D! d
    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)5 j0 ]/ X: b) ?9 _
    d[j]=opt(d[i
    / A) A: K  E( Z% H4 l
    ' a; a  b" C& B# t# V) h ][j 0 {2 Z9 e& J- A# [% i
    # n0 K  @, B- k, N) p" c
    ]+w(i ' v" V: a2 W6 U* T7 d5 N5 R. `9 B
      ^' N8 Q4 |& ]! `+ T! X; E$ o
    ,j
    5 v5 s- W1 }: V' j! t* W
    ' d* }6 R/ ]) A" j+ `! } ,i,j)∣0<=i
    - F( h" x! R3 E! o0 g- s$ K: s. Z; P3 q6 d* k3 J. I1 Y! w
    <i,0<=j % W$ |& |6 F% X/ v0 h1 I

    2 z; i# j! `  k2 N* n# F0 d0 {( D( { <j)
    / f; C5 J- E, }0 C4 J7 D) u8 J如图所示:
    / P; u1 O8 P. J9 \. _* q
    3 B3 j4 v. H9 C) I; P  e

    8 \5 o# [) v2 t0 p) N3 ?8 Y常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。
    & r" E2 s1 n6 y5 V; d对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n 2 M6 R3 G5 N/ S1 Y0 Y, |% s! z7 C
    t+e# l& H9 ^  {0 T
    ),空间复杂度是O ( n t ) O(n^t)O(n
    ; ]- ~5 d( ?) |: G7 q; K" E6 M- rt8 h* ~) n) w" x
    ) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。: E/ {8 z( h4 p, G2 p/ n
    3)计算几何
    % {  g; D0 r7 m0 l计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。# D8 K& w1 z+ B
    如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。
    7 s& ]" P; c4 w) |4 f0 r* i5 @/ l1、double 代替 float
    . |, |! F3 d" g# U9 |/ h% l1 o2 Pc++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;* ?$ K1 y' M5 M* A0 @
    2、浮点数判定) d( H5 g! W, b1 ^  s. p% S$ ]
    由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);! g% o) b5 o* p7 |% z
    两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:
    0 O5 m4 S* r0 w# |4 a/ i4 w+ Fconst double eps = 1e-8;9 J  A" D6 ~  A6 D  V* ]
    bool EQ(double a, double b) {
    " h' D* s) t7 `2 c0 ]    return fabs(a - b) < eps;
    4 n) w  D# J1 |4 N8 [# ^}, U+ a% n3 H# f# g
    1; K, z5 e/ i8 N3 v# V
    2( t" D/ q4 R( y9 a4 C' N, r6 ^) A
    3
    & ~9 G1 b6 {4 x& H8 {4
    , p/ g9 I# i( y& Y) Y5 \" f并且可以用一个三值函数来确定某个数是零、大于零还是小于零:
    + m+ N) ?6 _* n. G) gint threeValue(double d) {
    & S6 g$ C9 w" U# u% c* F    if (fabs(d) < eps)
    ! B: Y1 M3 v, c0 f0 s2 I" x7 C: a        return 0;
    + e) S& X+ L$ a+ d8 l: O    return d > 0 ? 1 : -1;$ r4 @+ |/ o+ ?" J5 S+ b! \: S
    }% b( ~3 ?: y$ Z3 B0 E* L; `0 g
    19 L# r7 H5 q) S, ?" H. `4 D+ A9 E4 r
    2
    % X0 `) T- m& T3 g" L" {6 l, |30 L' n3 U9 c7 t! o0 t6 v! t7 V
    4
    0 F1 _- ?2 |4 c% V5
    7 k' [4 \+ [6 v3 {' ^9 D3、负零判定/ q1 z  {  j5 e
    因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:
    8 e5 P" F1 N' s! r    double v = -0.0000000001;7 H1 e# A0 K% V% a+ \; f
        printf("%.2lf\n", v);+ Q* I7 i' X; X' d( D5 m
    1
    : u: t+ o$ U7 Y2
    2 O+ }  k( v& |( k3 W避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:
    % l! B6 j0 z/ w* d; X  \# c    double v = -0.0000000001;
    / }* x" c( W& b; h    if(threeValue(v) == 0) {$ n$ \: p" q0 N: c8 Y
            v = fabs(v);' Q8 y* b0 [5 E8 S0 {
        }
    7 O+ }7 n& Q! \, I. k    printf("%.2lf\n", v);
    9 d2 c; z4 i6 L; H* }( l1
    $ W% e4 {& q* V: N2, `* g9 T7 b( J  T2 S/ J
    3& ^- \; Y' h! L! p# Y- d
    41 g- h+ |/ D" F, t
    5
    * L) O% I# p  [; \. S4、避免三角函数、对数、开方、除法等
    . z0 S! p- g" B0 y* {: \9 p+ |c++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。
    , p, w# R  w3 ^  \( A5 k除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。
    % r' M( B" f# @8 U/ n$ ?5、系统性的学习
    . Y* U9 l* B: j& z/ T  J基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;( a3 ]1 g+ t$ t. E, U% i" O
    进阶知识:多边形面积、凸多边形判定、点在多边形内判定;; }) p" f" n4 {/ E5 n) n6 w; ?
    相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。: y: G" s) T8 ?, j
    $ j! F" ]3 u3 [5 P: |

    , T* u3 l( b: v" \# S) z& D学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。, e8 ?# `& Q! O
    4)数论' V+ O- u9 j. r, `: s3 I
    刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!
    " v$ X- Y$ a3 b. G# G5 d数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。
    ) \8 g; k# @0 R6 J4 Z( U9 z" j当然,数论也有简单问题,一般先做一些入门题提升信心。
    $ C7 z4 b- Y1 ~7 D1、数论入门
    % Q; y! t  k* ^主要是一些基本概念,诸如:
      X( O. X4 R$ {, S" d7 Q整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;
    5 z' @+ b+ T& a% E2 k. }' M. _* t2、数论四大定理, E; W% u" n7 W" N6 l
    这四个定理学完,可以KO很多题:+ b3 y0 p9 R3 V# [6 f9 d* o
    欧拉定理、中国剩余定理、费马小定理、威尔逊定理7 A" F3 V* _- W. C+ M
    3、数论进阶2 t  W4 _( g5 A  h7 t9 k" f; E7 h
    系统性的学习,基本也就这些内容了:
    ' U6 n4 a# L9 J扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。6 u+ ~+ C' {( N' Y6 V/ @  W
    5)字符串匹配6 V# q- A* O+ ~; g
    字符串匹配学习路线比较明确。( q- ?$ l( d" o9 M2 x
    先学习前缀匹配:字典树。# c- {9 N- a$ ?" K
    然后可以简单看一下回文串判定算法:Manacher。
    9 E1 k  d  ?' F: X2 u9 n" x6 v以及经典的单字符串匹配算法:KMP。. c1 O( ?5 m* I3 ?& n
    实际上平时最常用的还是 BM 算法,而ACM中基本不考察。; w! W, V  x! {( }: f; F
    然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。( W9 R4 \# b$ s! ~' i7 J, d# G
    关于 算法学习路线 的内容到这里就结束了。
    9 O' }' E- j/ @7 l% e" x0 h# a6 ~如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。
    ) d$ V  z- M1 I! F4 e参考资料& r" @; u5 o9 r; C
    【阶段一】C语言学习资料:《光天化日学C语言》(日更)
    ' h0 a& R1 N3 W! S: u【阶段二】C语言例题:《C语言入门100例》(日更)
    3 q7 q3 C, j: [! c【阶段三】算法入门题集:《LeetCode算法全集》(日更)
    2 b" o) ]( r- H- I* w【阶段四】算法进阶:《夜深人静写算法》(周更)
    9 U% ~1 T, r9 P9 r& m5 n/ A————————————————
    ( I. b/ t4 P8 z1 X版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。; n$ ~; W+ K; L# Y' ]
    原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/118382228$ F4 F% k9 L9 F+ J- u2 T* \5 h

    # m) |; D/ M5 X1 C$ @2 W5 w6 H" d8 s1 p" O5 Q& }: A& ]2 `4 I7 @
    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:19 , Processed in 0.352687 second(s), 55 queries .

    回顶部