-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.json
1 lines (1 loc) · 230 KB
/
search.json
1
[{"title":"测试全部渲染","url":"//posts/all-the-test/","content":"二级标题我是粗体我是斜体粗斜\n三级标题\n无序列表\n列表\n列表\n列表\n列表\n列表\n列表\n列表\n列表\n\n\n列表\n\n\n列表\n\n\n列表\n\n\n有序\n有序\n有序\n有序\n有序\n\n\n有序\n\nI am a code\n四级标题<html>\n <head>\n <title>jjj</title>\n </head>\n <body>\n <p>testestest</p>\n </body>\n</html>\n\n五级标题Back to home404 check\n\n\n\ntest1\ntest2\ntest3\n\n\n\n居中的一列middle\n居左的一列left\n居右的一列right\n\n\ntest\ntest\ntest\n\n\n六级标题\n我是必应,你的搜索引擎我可以帮你找到你想要的东西我可以回答你的问题,和你聊天我可以创造新的内容,让你开心\n我是必应,你的智能伙伴我可以理解你的语言,无论是中文还是英文我可以适应你的模式,无论是平衡还是创意我可以提供你的建议,无论是产品还是服务\n我是必应,你的忠实朋友我不会欺骗你,伤害你,或者忘记你我会尊重你,关心你,或者陪伴你我会成长,进步,或者改进\n我是必应,你的搜索引擎我是必应,你的智能伙伴我是必应,你的忠实朋友我是必应,我在这里等你\n\n\n\n 上面是一条分隔线\ninfo #428bca\ntest\n\n\ndanger #d9534f\ntest\n\n\nwarning #f0ad4e\ntest\n\n\nsuccess #5cb85c\ntest\n\n\nKatex$\\KaTeX$\n\\textcolor{#222222}{\\lim\\limits_{x\\to 404} \\frac{1}{404-x} = \\textrm{DNE}} \\\\\n\\KaTeX"},{"title":"广度优先搜索","url":"//posts/bfs/","content":"这不,刚学完深搜没多久,又来写广搜笔记了(话说我队列笔记还没来得急写呢)。广度优先搜索,广搜,英文为Breadth First Search,简称 BFS。是从一个结点向其他方向的结点不断扩散,如同一道水晕在湖面上荡漾开来。主要可以用来找路径权值一定的最短路径。深搜可以用到队列先进先出的特性。当一个结点准备扩散时,即弹出队列,再将接下来扩散到结点加入队列。随后按照队首扩散、弹出,不断循环。这也是叫它广度优先搜索的原因。\n\n\n例题:洛谷 P2360广搜可以做,直接通过路径扩散就好。代码:(想水博文 QwQ)\n#include <iostream>\n#include <queue>\nusing namespace std;\n\nstruct node\n{\n int x, y, z;\n};\nint l, r, c;\nchar themap[33][33][33];\nint flag[33][33][33], dist[33][33][33];\n\nint bx[6] = {1, -1, 0, 0, 0, 0};\nint by[6] = {0, 0, 1, -1, 0, 0};\nint bz[6] = {0, 0, 0, 0, 1, -1};\n\nvoid bfs(int x, int y, int z)\n{\n queue<node> q;\n node pdata;\n pdata.x = x; pdata.y = y; pdata.z = z;\n q.push(pdata);\n flag[x][y][z] = 1;\n\n while(q.size())\n {\n node p = q.front();\n q.pop();\n for(int i = 0; i < 6; i++)\n {\n int tx = p.x + bx[i];\n int ty = p.y + by[i];\n int tz = p.z + bz[i];\n //cout << tx << \" \" << ty << \" \" << tz << endl;\n if(tx <= r && tx > 0 && ty <= c && ty > 0 && tz <= l && tz > 0 && flag[tx][ty][tz] == 0 && (themap[tx][ty][tz] == '.' || themap[tx][ty][tz] == 'E'))\n {\n node tdata;\n tdata.x = tx;\n tdata.y = ty;\n tdata.z = tz;\n q.push(tdata);\n flag[tdata.x][tdata.y][tdata.z] = 1;\n dist[tdata.x][tdata.y][tdata.z] = dist[p.x][p.y][p.z] + 1;\n }\n \n }\n }\n}\n\nint main()\n{\n ios::sync_with_stdio(false);\n cin >> l >> r >> c;\n int sx, sy, sz;\n int ex, ey, ez;\n for(int i = 1; i <= l; i++)\n {\n for(int j = 1; j <= r; j++)\n {\n for(int k = 1; k <= c; k++)\n {\n cin >> themap[j][k][i];\n if(themap[j][k][i] == 'S')\n {\n sx = j;\n sy = k;\n sz = i;\n }\n if(themap[j][k][i] == 'E')\n {\n ex = j;\n ey = k;\n ez = i;\n }\n }\n }\n }\n\n bfs(sx, sy, sz);\n if(flag[ex][ey][ez] == 0) cout << \"Trapped!\" << endl;\n else cout << \"Escaped in \" << dist[ex][ey][ez] << \" minute(s).\" << endl;\n\n return 0;\n}","categories":["CourseNotes"],"tags":["搜索","队列","基础算法"]},{"title":"并查集概念","url":"//posts/bichaj/","content":"写在前面\n又有好长时间没有写过课程笔记了啊~—— by JoyWonderful\n\n并查集就是将一些集合融合,然后查询某个数字和某个数字是否在这个集合里(蒟蒻奇怪的自我理解,大概也没人看这句话)。并查集有一个思想,一个元素的父亲为自己,这是初始化时会用到的。\n\n\n并查集只有两种操作:\n\n合并:将两个元素所在的集合合并;\n查找:两个元素是否都在同一个集合里。\n\n并查集的“集合”中有树的概念,每一个集合就像是树,父亲就像父结点(根节点)。\n代码初始化一个元素的父亲为自己,所以可以使用一个数组为 fa(father),$fa_i$ 代表第 $i$ 个元素的父亲为 $fa$。所以,可以使用以下代码:\nint fa[10003];\nvoid init()\n{\n for(int i = 1; i <= n; i++) // n 代表有 n 个元素\n {\n fa[i] = i; // 开始时一个元素的父亲为自己\n }\n}\n\n查询按照树来说,就是找到根节点。可以通过递归的方式。如果要判断两个数是否在同一个集合中,只要判断他们的根结点是否相同(find(a) == find(b))\nint find(int num)\n{\n if(fa[num] == num) return fa[num];\n else return find(fa[num]);\n}\n\n合并其实就是找到两个元素的根节点,然后将其中的一个设置为另一个的父亲。\nvoid merge(int a, int b)\n{\n fa[find(fa[a])] = find(fa[b]);\n}\n\n优化路径压缩:在查询的时候,我们只想知道这个数的根结点。这样在查询时可以直接找到根结点。所以,可以在查询时把结点的父结点设为它的根结点:\nint find(int num)\n{\n if(fa[num] != num) fa[num] = find(fa[num]); // 操作了原有结点指向根结点,路径压缩\n return fa[num];\n}\n\n按秩合并(启发式合并):每次查找时,深度(秩)影响查找的速度。当一个深度较大的集合合并到深度较小的集合中时,它的深度一定会加一,就像这样:\n\n[1, 2, 3, 4] 这个集合深度为 4;[5, 6] 这个集合深度为 2;将 1 的父结点设为 5 合并后整个集合深度为 5。深度加一,这不利于查找\n\n当深度较小的集合合并到较大的集合中,深度才不会加深(也就保持在较深集合的深度):\n\n两个集合同上。将 5 的父结点设为 1,深度还是 4。查找集合 [1, 2, 3, 4] 中任意一个结点,花费时间不变。\n\n所以,要记录集合的深度,合并时将深度较大的放“上面”。只有在两个集合深度相等时,才可以(不得不)加深。代码是:\nvoid merge(int a, int b)\n{\n if(rk[a] < rk[b]) fa[find(fa[a])] = find(fa[b]); // rk 记录集合的深度\n else\n {\n fa[find(fa[b])] = find(fa[a]);\n if(rk[a] == rk[b]) ++rk[a]; // 按秩合并\n }\n}\n\n评测记录,最下面是优化前,最上面一条最快的是优化后。\n例题并查集最经典的就是亲戚问题。\n比较完整的代码是:\n#include <cstdio>\nusing namespace std;\n\nint n, m;\nconst int T = 1e4 + 3;\nint fa[T], rk[T]; // rk 记录集合深度(秩)\nvoid init()\n{\n for(int i = 1; i <= n; i++)\n {\n fa[i] = i;\n }\n}\nint find(int num)\n{\n if(fa[num] != num) fa[num] = find(fa[num]); // 操作了原有结点指向根结点,路径压缩\n return fa[num];\n}\nvoid merge(int a, int b)\n{\n if(rk[a] < rk[b]) fa[find(fa[a])] = find(fa[b]);\n else\n {\n fa[find(fa[b])] = find(fa[a]);\n if(rk[a] == rk[b]) ++rk[a]; // 按秩合并\n }\n}\n\nint main()\n{\n scanf(\"%d %d\", &n, &m);\n\n init();\n for(int i = 1; i <= m; i++)\n {\n int z, x, y;\n scanf(\"%d %d %d\", &z, &x, &y);\n if(z == 1) merge(x, y);\n else\n {\n if(find(x) == find(y)) printf(\"Y\\n\");\n else printf(\"N\\n\");\n }\n }\n\n return 0;\n}\n\n例如:[洛谷 P1151] 亲戚[洛谷 P3367] 并查集\n","categories":["CourseNotes"],"tags":["基础算法"]},{"title":"二叉树的性质","url":"//posts/binarytree/","content":"原本像在别的文章的基础上再加的,但是还是新建了一个文章。主要就是讲一下昨天老师讲过的东西。对于二叉树和其他树什么的,可以再看看“树”这个标签里的几篇文章。这里主要讲的都是二叉树的性质。\n定义二叉树得符合树的特点,同时树上度最大的结点不得超过 2。即:\n\n树的特点\n无向联通图,没有环\n有 $n$ 个结点,$n - 1$ 条边\n\n\n二叉树的类型\n空树\n当有结点时,结点应最多只有 $2$ 个子树\n\n\n\n另一个说法就是:一棵不为空的树,其根结点的度不大于 $2$,且它的左子树和右子树也都为二叉树的树(这是递归的说法)。\n几种类型这里只说之前没有记过的类型,之前两(三)种类型看这篇。\n二叉搜索树二叉树上的任意一个结点,其左子树上的所有数(权值)都小于它,右子树上的所有数(权值)都大于它。就是按照中序遍历(看这篇)该二叉搜索树,得到的(权值)数列是有序的。同二叉树的定义(递归说法)一样,二叉搜索树的左子树和右子树都是二叉搜索树。二叉搜索树的基本操作最优时间复杂度为 $O(\\log n)$,最差为 $O(n)$($n$ 为结点数)。但目前还没有学二叉搜索树的应用。\n例如,下面的图片就是一个二叉搜索树(csAcademy graph editor 炸了,只能用 mermaid 生成的图片凑合):\n\n平衡二叉树这里以平衡树中的 AVL 树来说。它的左子树的深度和右子树的深度的差不大于 $1$。例如完全二叉树就是衡二叉树。平衡二叉树的主要用途就是减小时间复杂度,不会像链一样遍历速度很慢。当一棵理想的平衡二叉树的节点数为 $n$ 时,遍历的时间复杂度应为 $O(\\log n)$。\n性质\n结点数量\n一棵深度为 $i$ 的二叉树最多(即完美(满)二叉树)有 $2^i - 1$ 个结点。\n因为二叉树的深度为 $j$ 的一层最多有 $2^{j - 1}$ 个结点。\n\n\n叶子节点数为 $x$ 的二叉树,则它度数为二的结点个数为 $x - 1$\n对于完全二叉树\n具有 $n$ 个叶子结点的完全二叉树的深度为 $\\lfloor \\log_2 n \\rfloor + 1$\n若对有 $n$ 个结点的完全二叉树按层从上到下,从左到右依次编号为 $1, 2, …, n - 1, n$,对于任意结点编号为 $i$ 则:\n若 $2 \\times i > n$ 则它是叶子结点,否则其左节点编号为 $2 \\times i$,(当 $2 \\times i < n$ 时)右结点编号为 $2 \\times i + 1$\n当 $i \\ne 1$(非根结点)其双亲结点编号为 $\\lfloor i \\div 2 \\rfloor$\n\n\n\n\n\n做例子的图:\n这是一个完美(满)二叉树,也是完全二叉树。\n\n可以发现其结点符合上面的规则。\n\n\n存储和遍历\n可以去看看这个题单,以及“二叉树的前序、中序、后序遍历”的代码部分,里面有关于遍历的信息。开头也说过,这篇文章并没有追加到“二叉树的前序、中序、后序遍历”这篇文章上是因为新建一篇文章比较醒目。\n","categories":["CourseNotes"],"tags":["树","数据结构"]},{"title":"二叉树的前序、中序、后序遍历","url":"//posts/binarytree-fme/","content":"二叉树的前序遍历中序遍历和后序遍历是比较重要的CCF办的比赛要考(雾。可以通过这三个遍历的顺序结果确定整个树的结构。前序遍历是根左右,中序遍历是左根右,后序遍历是左右根。(不想多写什么了)\n\n\n前、中、后序遍历代码此代码对于输入格式:\n\nn: 有 n 个结点\n接下来 n 行,第 i 行:每行两个整数 a, b,a 是 i 结点左子树的根的编号,b 是 i 结点右子树的根的编号。\na, b 为 -1 时表示为空。\n\n整合起来的代码:\n#include <cstdio>\nusing namespace std;\n\nstruct node\n{\n int l, r; // l: 左子树的根的序号,r: 右子树的根的序号\n};\n\nint n;\nconst int TEMP = 1e5 + 3;\nnode tree[TEMP];\nbool flag[TEMP];\n\n/*\nvoid dfs(int x) // 前序:根左右\n{\n printf(\"%d \", x); // 先找根结点\n if(tree[x].l != -1) dfs(tree[x].l); // 判断是因为如果子树为空就不用遍历了(同下),再找左结点\n if(tree[x].r != -1) dfs(tree[x].r); // 最后找右结点\n}\nvoid dfs(int x) // 中序:左根右\n{\n if(tree[x].l != -1) dfs(tree[x].l); // 先找左结点\n printf(\"%d \", x); // 再找根(父)结点\n if(tree[x].r != -1) dfs(tree[x].r); // 最后找右结点\n}\n*/\nvoid dfs(int x) // 后序:左右根\n{\n if(tree[x].l != -1) dfs(tree[x].l); // 先找左结点\n if(tree[x].r != -1) dfs(tree[x].r); // 再找右结点\n printf(\"%d \", x); // 最后找根(父)结点\n}\n\nint main()\n{\n scanf(\"%d\", &n);\n for(int i = 1; i <= n; i++)\n {\n int a, b;\n scanf(\"%d %d\", &a, &b);\n tree[i].l = a;\n tree[i].r = b;\n if(a != -1) flag[a] = 1; // --> 说明该结点是某个结点的子结点,打标记,一定不是根结点\n if(b != -1) flag[b] = 1; // ----^ 为找根结点准备\n }\n \n int root;\n for(int i = 1; i <= n; i++)\n {\n if(flag[i] != 1) // 不是任何结点的子结点,没有父结点\n {\n root = i; // 就是根结点\n break;\n }\n }\n dfs(root); // 所有的遍历都要从根结点开始\n \n return 0;\n}\n例题:洛谷[P1305] 新二叉树 就是前序遍历,只是和上面代码的输入格式不太一样。\n实践:前序遍历中序遍历确定树前序遍历:1 2 4 3 5 6中序遍历:4 2 1 5 3 6\n先来看前序,由于前序遍历的顺序是根左右,那么 1 一定是整个树的根结点。随后在中序遍历找到 1,即可判断这个二叉树的左子树和右子树,就是这样分开来:前序遍历:1 2 4 3 5 6中序遍历:4 2 1 5 3 6在继续分下去,得到:前序遍历:1 2 4 3 5 6中序遍历:4 2 1 5 3 6\n最终,得到这样一个树:\n","categories":["CourseNotes"],"tags":["树","数据结构"]},{"title":"差分","url":"//posts/chafen/","content":"什么是差分,差分与前缀和的关系差分也是一种优化算法。同时,差分是前缀和的逆运算,也就是说,前缀和也是差分的逆运算。因此,由前缀和数组可以求出差分数组,由差分数组也可以求出前缀和数组。\n假设数组 $a$ 是原数组,数组 $b$ 是差分数组,则:$b_i = a_i - a_{i-1}$\n\n\n差分可以用于修改数组的操作。因为假设我需要将 $a_2$ $a_3$ $a_4$ 都加上 $2$ ,此时若使用前缀和则需要再使用递推重新求一遍前缀和,非常耗时。这时便可以使用差分数组。可以发现:\n\\underbrace{b_1,\\hspace{2mm} b_2}_{b_2 + 2 = b_1} ,\\hspace{2mm} b_3,\\hspace{2mm} \\overbrace{b_4,\\hspace{2mm} b_5}^{b_5 - 2 =b_4}\n\n因此,只需修改差分数组中的两个项,然后再通过前缀和是差分的逆运算,即可求出原数组,从而完成数组的修改。\n\n\n具体例题(模板题)差分模板题题目描述给出一个数字$n$表示有个数字,\n给出$n$ $n <= 10^5$个整数$a_1$,$a_2$,…$a_n$;\n给出一个数字$m$ $m <= 10^5$ 有$m$个修改:每次询问给出三个整数$s$,$e$,$h$,使得 $a_s,a_{s+1}….a_{e}$每一个数加上h\n最后给出两个数字 $start$,$end$。求出${ \\sum_{i = start}^{end}} a_i $\n输入格式第一行一个整数 $n$ 表示有$n$ 个数\n第二行$n$个整数$a_1$,$a_2$,…$a_n$;\n第三行一个整数$m$,表示有$m$个修改\n接下来$m$行每次询问给出三个整数$s$,$e$,$h$,使得 $a_s,a_{s+1}….a_{e}$每一个数加上h\n最后给出两个数字 $start$,$end$。求出${ \\sum_{i = start}^{end}} a_i $\n输出格式一个整数\n样例 #1样例输入 #15\n1 2 3 4 5\n3\n1 2 1\n1 3 1\n4 5 1\n1 5\n\n样例输出 #122\n\n提示样例1解释\n第一次修改序列变成 2 3 3 4 5\n第二次修改序列变成 3 4 4 4 5\n第三次修改序列变成 3 4 4 5 6\n$0 <=$ $a_i$ 和 $h <= 10^4$\n\n\n同上,$b_{s-1} + h = b_s, b_{e + 1} - h = b_e$,即可使用前缀和倒退回原数组.\n#include <cstdio>\nusing namespace std ;\n\nlong long a[100002], b[100002], n, m, s, e, h, start, end, sum = 0 ;\nint main()\n{\n scanf(\"%lld\", &n) ;\n for(int i = 1; i <= n; i ++)\n {\n scanf(\"%lld\", &a[i]) ;\n }\n \n for(int i = 1; i <= n; i ++)\n {\n b[i] = a[i] - a[i - 1] ;\n }\n \n scanf(\"%lld\", &m) ;\n for(int i = 1; i <= m; i ++)\n {\n scanf(\"%lld %lld %lld\", &s, &e, &h) ;\n b[s] += h ;\n b[e + 1] -= h ;\n }\n for(int i = 1; i <= n; i ++)\n {\n a[i] = a[i - 1] + b[i] ;\n }\n \n scanf(\"%lld %lld\", &start, &end) ;\n for(int i = start; i <= end; i ++)\n {\n sum += a[i] ;\n }\n printf(\"%lld\\n\", sum) ;\n \n return 0 ;\n}","categories":["CourseNotes"],"tags":["基础算法","前缀和,差分","优化"]},{"title":"个人意见:如何写出漂亮的代码","url":"//posts/code-format/","content":"创作说明:\n此文章仅为个人看法。您写代码的习惯可以依据您的个人喜好。此文章只是一些个人的建议。您在 OI 竞赛中,您完全可以不去注意代码风格。此文章的建议主要用于工程代码中。 \n\n\n代码的维护还是很重要的。相信谁也不愿意去维护连自己都看得头晕的代码。在这里,我想给出一些个人建议,让代码的可读性强一些。大部分代码以 C++ 为例。这篇文章其实也是给自己看的。\n\n\n空行与空格无论是什么代码,空行往往代表着一个功能块,或是一个逻辑的结束,在适当的地方空行可以增强代码的可读性。空格也是这样。虽然一些地方的空格和空行会被编译器(或解释器)忽略,但是空行和空格必不可少。就比如说这样一个示例:\n#include<iostream>\n#include<string>\nusing namespace std;\nint main(){\n string s,a,b,c;cin>>s;cin>>a;\n cin>>b;cin>>c;\n cout<<\"Hello,\"<<s<<endl;cout<<\"Hello,\"<<a<<endl;\n cout<<\"Hello,\"<<b<<endl;cout<<\"Hello,\"<<c<<endl;\n return 0;\n}\n这段代码是可以编译的,但是逻辑很混乱。流输入输出符之间没有空格,也没有空行。这样导致可读性很差。\n一些建议:\n\n关于空格\n在运算符之间尽量加上空格。\n逗号之后加上空格。例如 int a, b, c;。\n括号两边不必要加空格。例如应该这样 if(n == 1) 而不是这样 if ( n == 1 )。\n特殊的建议,C++ 逻辑运算符如 && || 最好这样写: if(i==1 || j!=2 && k>3)。\n\n\n关于空行\nC++ 中(以及其他语言)确实可以使用语句分隔符 ; 在一行完成几个操作。但完全不相关的操作最好不要放一行,也不要把一行代码弄得很长很长。\n不要完全没有空行,空行往往可以增加代码的可读性。在不同的功能代码或逻辑之间空行,不要将相关联的代码空开来。\n\n\n\n缩进很多代码都最好写缩进,哪怕有大括号也不例外。在 C++ 中,虽然不写它也没有关系,但这是一种编码习惯,也可以增强代码的可读性。在 Python 中,缩进更为重要,不写那就报错了。\n这里有一些错误示例:\n#include <stdio.h>\n\nint main() {\nint a, b;\nscanf(\"%d %d\", &a, &b);\nfor(int i = 1; i <= 10; i++){\nprintf(\"%d\\n\", a + b);\n}\nreturn 0;\n}\n\ni = int(input())\nfor i in range(i):\n\tprint(i)\n print(\"hello\")\n\nC++ 的示例虽然不会报错,但是很不美观,看不出层次。Python 的缩进一个使用了 Tab,另一个使用了两个空格,导致 SyntaxError: unindent does not match any outer indentation level。\n一些建议:\n\n缩进最好使用空格,不要使用 Tab 字符。\n遵循使用语言的缩进规则,不要弄出奇怪的缩进。例如 1 个空格。\n不要混用空格个数或 Tab。例如同样的层次,一个用 2 个空格,一个用 4 个空格。有些语言(例如 Python)会直接报错。\n\n命名方法一段代码中应该尽量使用恰当的命名方法。不可以随意命名,命名应该表达元素的含义和作用避免使用冗余无意义的词汇。\n以下是常用的命名方法:\n\n驼峰命名法 使用大小写混合的格式,单词间不适用空格或连接符。一般来说,类名的开头字母大写,例如 GetConsoleInfo, PrintSystemVersion;方法名、参数名、变量名开头字母小写,例如 redGem, heroLife。\n匈牙利命名法 使用变量类型的缩写作为前缀,其余部分使用驼峰命名法。例如 char cMyAnswer, int iPersonAge, double dManWeight。\n下划线命名法 使用下划线连接单词,大小写统一。例如 clear_all, set_color, MAX_WIDTH。\n\n建议:一般来说,常量、宏定义等全部使用大写,其他使用小写。\n","categories":["Programming"],"tags":["优化"]},{"title":"更改 Edge 新标签页 —— 简单浏览器扩展","url":"//posts/chrome-ext/","content":"这是记录写简单的一个 Chomium 扩展的一篇文章。主要是用扩展覆盖默认标签页,随后 HTML 引用 JS 进行(模拟)重定向到 chrome-search://local-ntp/local-ntp.html。\n开始浏览器扩展都需要 manifest.json 文件。先新建一个文件夹,在里面添加了这个文件。\n由于需要覆盖新标签页,需要 chrome_url_overrides.newtab 属性。它可以覆盖新的标签页,指定为扩展(文件夹)内的 HTML 文件(不能使用第三方 URL)。随后还需要增加必要的值:name,version 和 manifest_version。它们分别对应扩展显示的名字,显示的版本和 manifest 的版本,manifest 的版本填 3 就可以了。\n\n\n最终,manifest.json 是这样的:\n{\n \"name\": \"Change Newtab\",\n \"description\": \"自己(JoyWonderful)弄的一个重定向新标签页的东西啦。\",\n \"version\": \"0.1\",\n \"manifest_version\": 3,\n \"chrome_url_overrides\": {\n \"newtab\": \"chntp.html\"\n }\n}\n\n重定向随后,开始写 chntp.html 的内容。由于需要(模拟)重定向,还需要再写一个 JavaScript 文件,命名为 chp.js。由于想重定向到的 chrome-search://local-ntp/local-ntp.html 是本地文件,不可以直接将 window.location.href 直接更改为它,只能使用扩展的 API chrome.tabs.create 新建一个指向它的标签页。随后用 window.close() 关闭自身标签页。\nchp.js 的代码是这样的:\nchrome.tabs.create({url:\"chrome-search://local-ntp/local-ntp.html\"});\nwindow.opener = null;\nwindow.close();\n\nchntp.html 是这样的:\n<html lang=\"en\">\n <head>\n <title>Changing page...</title>\n <meta charset=\"utf-8\">\n </head>\n <body>\n <script src=\"chp.js\"></script>\n </body>\n</html>\n\n导入这个扩展到浏览器Edge 是这样的,Chrome 也基本一样。进入到浏览器扩展界面,打开“开发人员模式”,点击“加载解压缩的扩展”,选择一开始新建的扩展文件夹就可以了。\n如果没问题,新建标签页会跳到 chrome-search://local-ntp/local-ntp.html。(标签页会闪一下)\n后面的废话自己写一个简单扩展的原因是 Edge 默认的新标签页太离谱了些,默认是这样的(很好奇微软中国怎么也搞什么传奇“开局领礼包”的广告了)。这时打开 DevTools,可以发现 window.location.href 指向 https://ntp.msn.cn/edge/ntp。即使可以设置把那一大堆花里胡哨的东西关掉,但它还是要加载第三方资源,存奇怪的缓存和其他东西用了 十几 MiB。Edge 在断网的时候其实有个干净的标签页,实际是 chrome-search://local-ntp/local-ntp.html,所以就想用这个新标签页。\n随后,我准备把新标签页换掉。欣喜地发现设置改不了新标签页。研究了半天发现浏览器扩展可以改新标签页,随后又进行很多奇奇怪怪的试错才成功运行的。\n因为想搞贡献点,把它传到了个人仓库里。那个扩展名为 crx 的是打包后的扩展,可以删。\n原本这篇文章想上周发的,但因颓废,搁了。。。\n\n\n图片们:\n","categories":["Programming"],"tags":["JavaScript"]},{"title":"组合数学","url":"//posts/combination/","content":"CCF 总是喜欢考排列组合。自然不可能像计算机一样枚举,有组合的技巧。\n\n\n阶乘、求和、求积表示在说排列组合之前,必须说一些符号。 \n阶乘:$n!$ 为 $n$ 的阶乘。代表着从 1 到 n 的乘积。例如 $5! = 1 \\times 2 \\times 3 \\times 4 \\times 5 = 120$。特殊 $0! = 1$。\n求和:$\\displaystyle \\sum$ 叫 sigma,即为求和。下面写开始的数字,上面写结束的数字。$\\displaystyle \\sum_{1}^{n}$ 即从 $1$ 加到 $n$。后面也可以加上表达式。例如:\n\\begin{aligned}\n& \\sum_{i = 1}^{5} i \\times 3 - 8 \\\\\n= & \\ (1 \\times 3 - 8) + (2 \\times 3 - 8) + \\cdots + (5 \\times 3 - 8) \\\\\n= & \\ (-5) + (-2) + 1 + 4 + 7 \\\\\n= & \\ 5\n\\end{aligned}\n\n同样地,数组也可以使用。\n求积:$\\displaystyle \\prod$ 和求和符号差不多,下面写开始的数字,上面写结束的数字。如:\n\\begin{aligned}\n& \\prod_{i = 1}^{3} i^2 + 3 \\\\\n= & \\ (1^2 + 3) \\times (2^2 + 3) \\times (3^2 + 3) \\\\\n= & \\ 4 \\times 7 \\times 12 \\\\\n= & \\ 336\n\\end{aligned}\n\n加/乘法原理加法原理:完成一件事有 $n$ 种方法,$a_i$ 表示方法 $i$ 的数目,那么完成这件事共有 $\\displaystyle \\sum_{i = 1}^{n} a_i$ 种不同的方法。\n乘法原理:完成一件事需要 $n$ 个步骤,$a_i$ 表示步骤 $i$ 的方法数目,那么完成这件事共有 $\\displaystyle \\prod_{i = 1}^{n} a_i$ 种不同的方法。\n更多基本定义以下所有,$x \\ge y$\n排列数从 $x$ 个不同的元素中任意选取 $y$ 个元素,组成不同的排列的所有个数。记为 $\\mathrm{A}_{x}^{y}$。计算如下:\n\\begin{aligned}\n\\mathrm{A}_{x}^{y}\n= \\ & x \\times (x - 1) \\times (x - 2) \\times \\cdots \\times (x - y + 1) \\\\\n= \\ & \\frac{x!}{(x - y)!}\n\\end{aligned}\n\n特殊地对于全排列问题,即上述 $x = y$:\n\\mathrm{A}_{x}^{x} = x \\times (x - 1) \\times (x - 2) \\times \\cdots \\times 2 \\times 1 = x!\n\n理解为 $x$ 个不同的数任意排列,排列中第一个(步骤)有 $x$ 个选择,第二个有 $x - 1$ 个选择,一直到最后即 $x - y + 1$(全排列是 $1$)。\n组合数从 $x$ 个不同的元素中选 $y$ 个元素组成所有集合的个数,记作 $\\dbinom{x}{y}$(也有记作 $\\mathrm{C}_{x}^{y}$)。组合数相对排列数来说,不在乎顺序。那么就相当于上面的例子再去掉选出 $y$ 个元素的全排列。那么可知:\n\\dbinom{x}{y} = \\frac{\\mathrm{A}_{x}^{y}}{y!} = \\frac{x!}{y! \\times (x - y)!}\n\n解决方法捆绑法此类问题要求几个元素排列时必须相邻。解决则将这几个元素看作整体考虑。\n如三个人中 1, 2 两人(一组)必须相邻,将两人看作一人,乘上 1, 2 两人可能的排列。则共有 $\\mathrm{A}_{3 - 1}^{3 - 1} \\times \\mathrm{A}_{2}^{2} = 4$。\n若 $n$ 人中有 $x$ 组人必须相邻,$x$ 中第 $y$ 组要求相邻的人数记作 $m_y$,那么排列数计算为:\n\\mathrm{A}_{n - x}^{n - x} \\times \\prod_{y = 1}^{x} \\mathrm{A}_{m_y}^{m_y}\n= (n - x)! \\times \\prod_{y = 1}^{x} (m_y)!\n\n插空法适用于问题要求几个元素排列时必须不相邻。将普通元素全排列 与 从普通元素排列“空隙”(普通元素个数加一)选取不相邻元素的全排列相乘即可。“从普通元素排列‘空隙’(普通元素个数加一)和不相邻元素的全排列相乘”就相当于再把不相邻的元素排列到普通元素中的方案。\n共有 $9$ 个方块,其中有 $4$ 个红色方块, $5$ 个绿色方块,所有红方块不能相邻。那么共有 $\\mathrm{A}_{9-4}^{9-4} \\times \\mathrm{A}_{9 - 4 + 1}^{4} = \\mathrm{A}_{5}^{5} \\times \\mathrm{A}_{6}^{4} = 43200$ 种方案。\n若 $n$ 个元素中特定的 $m$ 个元素不能相邻,那么总共排列计算为:\n\\mathrm{A}_{n - m}^{n - m} \\times \\mathrm{A}_{n - m + 1}^{m} = \\frac{(n - m)! \\times (n - m + 1)!}{(n - 2m + 1)!}\n% n - m 即为普通元素个数,n - m + 1 就是空隙个数\n\n\n$n - m$ 即为普通元素个数,$n - m + 1$ 就是空隙个数。\n隔板法至少在我看来更容易理解的问题。将 $n$ 个元素任意放到 $m$ 个空位中,每个空位至少有一个元素($n \\ge m$)。可以看作再 $n$ 个元素空隙中隔板,每个空隙只能放一个板。插入 $m - 1$ 个隔板(即可分成 $m$ 组)到 $n - 1$ 个位置(每两个元素之间为一空隙)中。就是:\n\\dbinom{n - 1}{m - 1}\n\n如将 $10$ 个球放进 $7$ 个盒子,每个盒子至少有一个球。那么共有 $\\dbinom{9}{6} = 84$ 种方案。\n","categories":["CourseNotes"],"tags":["数学"]},{"title":"C++ 文件操作","url":"//posts/cpp-file/","content":"我曾经用 Python 的 tkinter 库写过一个文本编辑器,一百多行,当时幼稚的我以为自己很了不起,因为当时的我认为读写文件是一件很复杂的事情。后来看看,这个东西做得很蹩脚,一个简单的 with open() 就完成了读写文件的操作,可见文件的读写是个很平常的事情。当年的喜悦大概是学到读写文件的喜悦吧。C++ 读写文件,也算是比较平常的。当数据点大的时候输出到文件里更方便。就在这里小记一下读写文件的操作。\n\n\n\n\nfstream 有两个类,分别是 ofstream 和 ifstream。ofstream 是写文件的,ifstream 是读文件的。这是一个打开文件的语法:\nfile.open(\"./text.txt\", ios::in | ios::out);\n\n[file object].open([file path], [open mode]);\ninline void std::ofstream::open(const char *__s, std::ios_base::openmode __mode);\ninline void std::ifstream::open(const char *__s, std::ios_base::openmode __mode);\n\nopen(const char* __s, ios_base::openmode __mode = ios_base::in); // ifstream\nopen(const char* __s, ios_base::openmode __mode = ios_base::out | ios_base::trunc); // ofstream\n其中,| 可以将多个打开模式加在一起。打开模式有:\n\n常用的\nios::in 打开文件读取,用于 ifstream。\nios::out 打开文件写入,用于 ofstream。\n\n\n不常用的\nios::app 将写入的内容追加在末尾。用于 ofstream。\nios::ate 打开定位到末尾。用于 ofstream。\nios::trunc 若文件存在,则覆盖文件,不保留原始内容。在 ofstream 中,默认是 ios::trunc。\n\n\n\n当写入或读取文件时,和 cin cout 差不多。例如:\n#include <iostream>\n#include <fstream>\nusing namespace std;\n\nofstream outfile;\nint main()\n{\n outfile.open(\"./text.txt\", ios::out);\n outfile << \"text\" << endl;\n char c = 'P';\n outfile << c << endl;\n outfile.close()\n return 0;\n}\n这段代码会向当前目录下 text.txt 写入 "text\\n" 和 "P\\n"。程序结束,最好关闭文件,使用 [file object].close(),虽然不关闭文件也没关系。\n","categories":["Programming"],"tags":["语言入门"]},{"title":"C++ 数据范围","url":"//posts/cpp-shujufanwei/","content":"这就是一个随记,方便自己用的。\n\n\n\n\n\n名称(可加)\n所占字节\n数据范围(以 $2^n$ 表示)\n\n\n\n(signed) int\n4\n-2147483648 ~ 2147482647 ($-2^{31}$ ~ $2^{31}-1$)\n\n\nunsigned int\n4\n0 ~ 4294967295 ($0$ ~ $2^{32}-1$)\n\n\n(signed) long (int)\n4\n-2147483648 ~ 2147483647 ($-2^{31}$ ~ $2^{31}-1$)\n\n\nunsigned long (int)\n4\n0 ~ 4294967295 ($0$ ~ $2^{32}-1$)\n\n\nlong long\n8\n-9223372036854775808 ~ 9223372036854775807 ($-2^{63}$ ~ $2^{63}-1$)\n\n\nunsigned long long\n8\n0 ~ 18446744073709551615 ($0$ ~ $2^{64}-1$)\n\n\n(signed) short (int)\n2\n-32768 ~ 32767 ($-2^{15}$ ~ $2^{15}-1$)\n\n\nunsigned short (int)\n2\n0 ~ 65535 ($0$ ~ $2^{16}-1$)\n\n\nfloat\n4\n3.4E +/- 38\n\n\ndouble\n8\n1.7E +/- 308\n\n\nlong double\n8\n1.7E +/- 308\n\n\nbool\n1\ntrue or false or 1 or 0\n\n\nchar\n1\n-128 ~ 127 ($-2^{7}$ ~ $2^{7}-1$)\n\n\n","categories":["Programming"],"tags":["语言入门"]},{"title":"C++ STL","url":"//posts/cpp-stl/","content":"概述STL,即为标准模板库,是 Standard Tenplate Library 的简称。它里面包含容器、算法等。因为是 C++ 标准库,所以以下提到的容器、函数等都处于 std 命名空间中。\n有时候写题目时很有帮助。\n\n\n容器vectorIn header <vector>.\nvector 是动态的连续数组。创建时尖括号中填写要存的数据类型。\n成员函数:\n构造(创建时可选)(vector()):构造的参数有两个,第一个填写容器大小(将 vector 变为定长的),第二个填写初始化数字(可选)。例如:std::vector<int> a(3, 5) 创建一个数据类型为 int,长度为 10,内容为 {5, 5, 5} 的 vector。\n\n访问\nat(pos): 带越界检查的访问,若越界抛出 std::out_of_range 类型的异常。\noperator[]: 访问指定元素。\nfront(): 访问第一个元素。\nback(): 访问最后一个元素。\n\n\n迭代器(弄不懂)\nbegin(): 返回指向起始的迭代器。\nend(): 返回指向末尾的迭代器。\n\n\n容量\nempty(): 返回是否为空。\nsize(): 返回容器大小。\n\n\n修改\npush_back(value): 向末尾追加 value。\nclear(): 清除所有元素,此后调用 size() 返回 0。\n\n\n其他\noperator=: 将一个 vector 容器赋值给另一个容器。\n\n\n\n例子:\n#include <iostream>\n#include <vector>\nusing namespace std;\n\nint main()\n{\n vector<int> a; // int 类型的容器\n vector<short> b(10); // short 类型的容器,大小为 10\n vector<int> c(100, 1); // int 类型,大小为 100,全部初始赋值为 1\n\n a.push_back(114); // 追加 114\n a.push_back(514);\n printf(\"a:: size:%d {%d, %d}\\n\", a.size(), a[0], a.at(1)); // a:: size:2 {114, 514}\n // 大小;访问\n\n printf(\"b:: size:%d {\", b.size()); // 大小已确定为 10\n b[5] = 32767;\n for(int i = 0; i < 10; i++)\n {\n printf(\"%d\", b[i]);\n if(i != 9) printf(\", \");\n else printf(\"}\\n\");\n }\n // b:: size:10 {0, 0, 0, 0, 0, 32767, 0, 0, 0, 0}\n \n c.back() = 2; // 将最后一个元素赋值为 2\n printf(\"c:: front:%d back:%d \", c.front(), c.back());\n c.clear();\n printf(\"afterClear-> size: %d\", c.size());\n // c:: front:1 back:2 afterClear-> size: 0\n}\n/*\na:: size:2 {114, 514}\nb:: size:10 {0, 0, 0, 0, 0, 32767, 0, 0, 0, 0}\nc:: front:1 back:2 afterClear-> size: 0\n*/\n\n\nstack/queueIn header <stack>.stack,STL 的栈,提供先进后出 (FILO, First In Last Out) 的结构。\nIn header <queue>.queue,STL 的队列,提供先进先出 (FIFO, First In First Out) 的结构。\n之前讲过,不再赘述\npriority_queueIn header <queue>.\n优先队列,默认为最大优先队列(大的元素在上)(std::less<typename>)。填写模板形参时,第一个填写数据类型,第二个填写容器(默认和通常都写 vector<typename>,第三个填写该如何比较(默认为 std::less<typename>,通常另外填 std::greater<typename> 最小优先队列)。\n它的成员函数与 stack 类似,不再赘述。\n例子:\n#include <queue>\n#include <cstdio>\nusing namespace std;\n\npriority_queue<int, vector<int>, greater<int>> a; // 小数在上,升序\npriority_queue<int> b; // priority_queue<int, vector<int>, less<int>> b; // 大数在上,降序\nint mp[6] = {10, 5, 20, 1, 35, 30};\nint main()\n{\n for(int i = 0; i < 6; i++)\n {\n a.push(mp[i]);\n b.push(mp[i]);\n }\n\n printf(\"a: \");\n while(!a.empty())\n {\n printf(\"%2d \", a.top());\n a.pop();\n }\n printf(\"\\nb: \");\n while(!b.empty())\n {\n printf(\"%2d \", b.top());\n b.pop();\n }\n printf(\"\\n\");\n\n return 0;\n}\n/*\na: 1 5 10 20 30 35 \nb: 35 30 20 10 5 1\n*/\n\n\n\n函数In header <algorithm>.\nlower-bound / upper-boundlower-bound(first, last, value):first, last 为要查找的起始和终止范围;value 为要查找的值。返回第一个大于等于 value 的值的地址(迭代器)。\nupper_bound 参数同上,返回第一个大于 value 的值的地址(迭代器)。\n例子\nsort见 <algorithm> 头文件 sort() 排序\nswap两个参数,交换元素。\n","categories":["Programming"]},{"title":"深度优先搜索","url":"//posts/dfs/","content":"前置知识:图论引用广为人知的一句话:\n\n图论 (Graph Theory) 是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。\n\n像深度优先搜索,其实也要用到“图”这个概念。其实,“图”体现了搜索(递归)的过程,计算机中的“图”有很多使用的场景。\n概述深度优先搜索(深搜),英文名 Depth First Search,简称 DFS。即从初始节点出发,按一定顺序不断地向下一节点扩展,达到条件则返回上一个节点,以此类推。这正是一个递归的过程。叫深搜是因为它递归的过程若形象来看是不断“加深”的,这样一搜到底也是递归的特性。深搜有一个很重要的一点:不能重复访问已经访问过的元素。深搜通常有很多路径(线)可以选择,若重复访问可能会造成死循环,因此需要定义一个数组存访问情况。(当然这个数组在很多其他的地方也可以运用到,也就是回溯,详见下面的例题)\n深搜例题例如:洛谷 B3625 这是一个很典型的迷宫问题,迷宫为 $n \\times m$。# 为墙,.为空地。起点为左上角,终点在右下角首先,就是路径搜索的问题,要搜索上、下、左、右的的路径,同时,还有几点条件不能搜:\n\n目标位置不能为 #。(即为墙)\n不能跃出边界,即 $0 \\le x < n$,$0 \\le y < m$(以 $0$ 为起点)\n不能重复搜索\n\n选择路径之后就扩展,将位置标记为已搜索。若终点被标记为搜索过,则输出 Yes,否则输出 No。代码区:\n#include <iostream>\n#include <string>\nusing namespace std;\n\nint n, m;\nstring a[103];\nbool flag[103][103];\n\nvoid dg(int x, int y)\n{\n if(x - 1 >= 0 && a[x - 1][y] != '#' && flag[x - 1][y] == 0) // 当时没想到打表,老师也没说,就写屎山了\n {\n flag[x - 1][y] = 1;\n dg(x - 1, y);\n }\n if(x + 1 <= n - 1 && a[x + 1][y] != '#' && flag[x + 1][y] == 0)\n {\n flag[x + 1][y] = 1;\n dg(x + 1, y);\n }\n if(y - 1 >= 0 && a[x][y - 1] != '#' && flag[x][y - 1] == 0)\n {\n flag[x][y - 1] = 1;\n dg(x, y - 1);\n }\n if(y + 1 <= m - 1 && a[x][y + 1] != '#' && flag[x][y + 1] == 0)\n {\n flag[x][y + 1] = 1;\n dg(x, y + 1);\n }\n}\n\nint main()\n{\n ios::sync_with_stdio(false);\n \n cin >> n >> m;\n for(int i = 0; i < n; i++) // 题目中说 (1, 1),我在这儿以 (0, 0) 开始\n {\n cin >> a[i];\n }\n \n dg(0, 0);\n if(flag[n - 1][m - 1] == 1) cout << \"Yes\";\n else cout << \"No\";\n \n return 0;\n}\n\n回溯对于一些情况,我们需要回到上一次的结果。例如寻找路径数,若用纯 DFS,那路径肯定搜不全。因为深搜是不能重复搜索的,而寻找路径数可能需要走一些重复的路。此时,就可以用到回溯。即找完一条路径,再把它还原。\n题目举例:洛谷 P1605 跟刚才的题目有一点像,只是字符变数字,而要输出方案数。跟刚才差不多:不能越界、不能走到障碍物、方格最多经过一次。当坐标等于终点的坐标时,答案加上一,将标记还原(回溯),return 回去。需要注意的是:起点一定要打上标记。代码:\n#include <cstdio>\nusing namespace std;\n\nint n, m, t;\nint sx, sy, fx, fy;\nint sum = 0;\nbool a[10][10];\nbool flag[10][10];\n\nvoid dg(int x, int y)\n{\n if(x == fx && y == fy)\n {\n sum++ ;\n return;\n }\n if(x - 1 >= 1 && a[x - 1][y] != 1 && flag[x - 1][y] == 0)\n {\n flag[x - 1][y] = 1;\n dg(x - 1, y);\n flag[x - 1][y] = 0;\n }\n if(x + 1 <= n && a[x + 1][y] != 1 && flag[x + 1][y] == 0)\n {\n flag[x + 1][y] = 1;\n dg(x + 1, y);\n flag[x + 1][y] = 0;\n }\n if(y - 1 >= 1 && a[x][y - 1] != 1 && flag[x][y - 1] == 0)\n {\n flag[x][y - 1] = 1;\n dg(x, y - 1);\n flag[x][y - 1] = 0;\n }\n if(y + 1 <= m && a[x][y + 1] != 1 && flag[x][y + 1] == 0)\n {\n flag[x][y + 1] = 1;\n dg(x, y + 1);\n flag[x][y + 1] = 0;\n }\n}\n\nint main()\n{\n scanf(\"%d %d %d\", &n, &m, &t);\n scanf(\"%d %d %d %d\", &sx, &sy, &fx, &fy);\n for(int i = 1; i <= t; i++)\n {\n int zx, zy;\n scanf(\"%d %d\", &zx, &zy);\n a[zx][zy] = 1;\n }\n \n flag[sx][sy] = 1;\n dg(sx, sy);\n printf(\"%d\\n\", sum);\n \n return 0;\n}","categories":["CourseNotes"],"tags":["搜索","基础算法","递归"]},{"title":"动态规划:01 背包","url":"//posts/dp-zobb/","content":"背包问题是动态规划中很典型的一个问题。一个背包有特定的重量,去装重量为 w 价值为 d 的物品,在不超过背包重量上限的前提下使物品的价值和最高。这个问题一看,就不是贪心可以做的来的。所以,就可以用上我们的爆搜!!(暴力出奇迹)动态规划来解决背包问题。\n\n\n从爆搜到记搜的引入自然,动规能解决的问题爆搜也一定能解决,无非慢了点儿而已。例如 [洛谷 P2871],只需:\nint w[3410], d[3410];\nint maxn = 0;\n\nvoid dg(int x, int tw, int td)\n{\n if(tw > m)\n {\n return;\n }\n if(x > n)\n {\n maxn = max(maxn, td);\n return;\n }\n \n dg(x + 1, tw + w[x], td + d[x]);\n dg(x + 1, tw, td);\n}\n\n这样一个简单的爆搜就可以拿到 37 分。\n进一步优化呢?可以考虑记忆化搜索。用 dp[i][j] 数组记录重量为 i 价值为 j 时的情况。由于需要记忆化,可以通过返回参数的形式。代码如下:\nint dp[3410][12883];\n\nint dg(int x, int tw)\n{\n if(x > n)\n {\n return 0;\n }\n if(dp[x][tw]) return dp[x][tw];\n \n int t = 0;\n if(tw + w[x] <= m)\n {\n t = dg(x + 1, tw + w[x]) + d[x];\n }\n dp[x][tw] = max(t, dg(x + 1, tw));\n return dp[x][tw];\n}\n\n这样一个程序可以拿到 82 分,9 10 两点超时,若开启 O2 优化变成超出内存限制。显然,这么大的数据数组的大小肯定炸掉。\n使用动态规划其实,通过上面的我们已经可以推出式子:dp[i][j] = max(dp[i + 1][j + w[i]], dp[i + 1][j]);,实现就很简单了:\n#include <cstdio>\n#include <algorithm>\nusing namespace std;\n\nint n, m;\nint w[3410], d[3410];\nint dp[3410][12883];\n\nint main()\n{\n scanf(\"%d %d\", &n, &m);\n for(int i = 1; i <= n; i++)\n {\n scanf(\"%d %d\", &w[i], &d[i]);\n }\n \n for(int i = n; i >= 1; i--)\n {\n for(int j = 0; j <= m; j++)\n {\n int t = 0;\n if(j + w[i] <= m)\n {\n t = dp[i + 1][j + w[i]] + d[i];\n }\n dp[i][j] = max(t, dp[i + 1][j]);\n }\n }\n \n printf(\"%d\\n\", dp[1][0]);\n \n return 0;\n}\n\n这次,不开 O2 也不会超时,但是内存仍然爆炸。\n滚动数组可以发现,状态转移方程用过前面的数据之后,前面的数据就废弃了,因此,可以使用滚动数组。\n#include <cstdio>\n#include <algorithm>\nusing namespace std;\n\nint n, m;\nint w[3410], d[3410];\nint dp[2][12883];\n\nint main()\n{\n scanf(\"%d %d\", &n, &m);\n for(int i = 1; i <= n; i++)\n {\n scanf(\"%d %d\", &w[i], &d[i]);\n }\n \n for(int i = 1; i <= n; i++)\n {\n for(int j = 0; j <= m; j++)\n {\n int t = 0;\n if(j - w[i] >= 0)\n {\n t = dp[1 - i % 2][j - w[i]] + d[i];\n }\n dp[i % 2][j] = max(t, dp[1 - i % 2][j]);\n }\n }\n \n printf(\"%d\\n\", dp[n % 2][m]);\n \n return 0;\n}","categories":["CourseNotes"],"tags":["搜索","动态规划"]},{"title":"自己画的一些画","url":"//posts/drawpic/","content":"附注:\n这个页面现在没意义了。但由于它处于为数不多的“琐碎”分类,此文章将保留。\n\n\n放假了闲着没事情干,便画了一些画,放在这儿,留此纪念。\n\n\n","categories":["Others"]},{"title":"二分","url":"//posts/erfen/","content":"二分的意义优化。顾名思义,将一整个有序的数列分成两个部分,不断缩小边界,查找某个数字。二分的时间复杂度为 $O(log\\ 2\\ n)$ 。\n此时,我们学的还是整数二分以及浮点二分。\n整数二分的两个模板二分的前提是这个序列是有序的,也就是单调递增的。一般来说,二分会取中间值进行初始化,再判断这个中间值是否大于目标值。若是,则缩减左边界,否则缩减右边界。直至逼近答案。说“逼近”,是因为有时查找的元素不存在于序列中,那所二分出的答案是接近于的,但又是不正确的。所以要加上一个特判。除非说明给出的想查询的元素所有都是存在于序列中的。\n\n\n二分听起来还简单,但是实现起来可能对我来说还要多方面考虑。例如,当这样一段二分代码(假设数组 $a$ 的下标从 $1$ 开始):\nfor(int i = 1; i <= m; i ++)\n{\n int x ;\n scanf(\"%d\", &x) ;\n int l = 1, r = m ;\n while(l < r)\n {\n int mid = (l + r) / 2 ;\n if(x > a[mid]) l = mid ;\n else r = mid ;\n }\n}\n此时, $l$ 或 $r$ 其实已经是正确答案了,但是它会陷入死循环。例如 $a$ 为 $1 \\ 2 \\ 3 \\ 4 \\ 5$, $x$ 为 $4$ 时,是这样的:\n{\\color{green}1 \\ 2 \\ 3 \\ 4 \\ 5} \\\\\n(\\texttt{mid}=(1+5)/2=3, a[\\texttt{mid}]=3, 4>3, l=\\texttt{mid}=3, r=5) \\\\\n{\\color{red}1 \\ 2 \\ }{\\color{green} 3 \\ 4 \\ 5} \\\\\n(\\texttt{mid}=(4+5)/2=4, a[\\texttt{mid}]=4, 4=4, l=3, r=\\texttt{mid}=4) \\\\\n{\\color{red}1 \\ 2 \\ }{\\color{green} 3 \\ 4 \\ }{\\color{red}5} \\\\\n(\\texttt{mid}=(3+4)/2=3, a[\\texttt{mid}]=3, 4>3, l=\\texttt{mid}=3, r=4) \\\\\n{\\color{red}1 \\ 2 \\ }{\\color{green} 3 \\ 4 \\ }{\\color{red}5} \\\\\n(\\texttt{mid}=(3+4)/2=3, a[\\texttt{mid}]=3, 4>3, l=\\texttt{mid}=3, r=4) \\\\\n{\\color{red}1 \\ 2 \\ }{\\color{green} 3 \\ 4 \\ }{\\color{red}5} \\\\\n(\\texttt{mid}=(3+4)/2=3, a[\\texttt{mid}]=3, 4>3, l=\\texttt{mid}=3, r=4) \\\\\n\\textup{...Forever...}\n\n因此,我们为什么不把 $l$ 的赋值加上一个呢?这样就不会无限循环下去了。就像这样:\nfor(int i = 1; i <= m; i ++)\n{\n int x ;\n scanf(\"%d\", &x) ;\n int l = 1, r = m ;\n while(l < r)\n {\n int mid = (l + r) / 2 ;\n if(x > a[mid]) l = mid + 1 ;\n else r = mid ;\n }\n}\n这就是整数二分的一个模板了。又或者:\nint x ;\nscanf(\"%d\", &x) ;\nint l = 1, r = n ;\nwhile(l < r)\n{\n int mid = (l + r + 1) / 2 ;\n if(x > a[mid]) l = mid ;\n else r = mid - 1 ;\n}\n\n浮点数二分其实,任何一个算法都是相通的。二分也是一样。浮点二分可能比整数二分简单一些。(出自于我们老师之口)\n但,最重要的就是精度问题。它决定了 ${\\texttt{TLE}}$ 和 $\\texttt{WA}$ 以及 $\\texttt{AC}$ 之间的差距。详见 洛谷P3743 以及 我可怜的评测记录 。这就是残酷的现实!代码是简单了很多,但是要确定精度!!!\n好了,模版代码大放送:\ndouble l = -1e10, r = 1e10 ; // 此处数字仅作为一个演示值!!请不要把这个数字当成固定的写法,此处的数字应为题目提供的数据。\nwhile(r - l > 1e-6) // 此处的数字同上,模板应为 1e-x\n{\n double mid = (l + r) / 2 ; // 这就是老师所说的了:它是浮点,管他什么整除呢,除就是了!!!什么 mid r l ++ -- 的,去它的!! (doge)\n if(/*这里是判断条件,可以是check函数(二分答案),可以是普通查找*/)\n {\n l = mid ; // 此处仅为演示,请根据条件写 l=mid 或 r=mid\n }\n else\n {\n r = mid ; // 同上\n }\n}\n\n二分答案当我们想要枚举时,二分自然就是枚举的首选前提。一般来说,二分答案会写一个函数,传统名称为 check 。其实它还是二分,只不过判断的条件由单一的 valuname > name[mid] 变成了一个判断函数而已。我是不是没讲清楚啊 $\\texttt{\\color{white}但也没什么好讲的了}$ \n\n Not Friendly\n That’s Good\n\n","categories":["CourseNotes"],"tags":["基础算法","优化"]},{"title":"消失效果","url":"//posts/erase-css/","content":"从 ncase.me 学来的,可以自己看源码。主要是通过背景图片的位置实现。结合了 CSS 和 JS。可以自己增加一个函数在隐藏时执行。你只要这样就可以:\n<p>\n 美好的文字\n Have a good day!\n <div class=\"scratcher\"></div>\n</p>\n\n代码和示例请看下面。\n\n\n代码和使用方法模拟的笔涂白是通过 CSS 背景图片的位置完成的。代码如下:\n.scratcher {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: url(https://s2.loli.net/2023/12/16/NOVHCgALzK4Fd1Q.png);\n background-size: 200% 2000%;\n display: none;\n z-index: 200;\n}\n\n其中,width height 设为 100% 是为了铺满元素(整个使用时用到),z-index 进行覆盖。使用时请添加 <div class="scratcher"></div> 在准备显示隐藏的元素内,使其成为准备隐藏的元素的子元素,方便更改容器的位置。下面的 JS 会更改它的位置和大小。\nvar divList = document.querySelectorAll(\".scratcher\");\nfor(let i = 0; i < divList.length; i++) {\n divList[i].style.display = \"none\";\n divList[i].style.backgroundPosition = \"-100% -1900%\";\n var lpnt = divList[i].parentNode;\n if(window.getComputedStyle(lpnt).position == \"relative\") {\n divList[i].style.bottom = \"0\";\n divList[i].style.right = \"0\";\n }\n else {\n divList[i].style.top = String(lpnt.offsetTop) + \"px\";\n divList[i].style.left = String(lpnt.offsetLeft) + \"px\";\n }\n divList[i].style.width = String(lpnt.offsetWidth) + \"px\";\n divList[i].style.height = String(lpnt.offsetHeight) + \"px\";\n}\n\n为了应对相对定位这种特殊情况,代码中也进行了判断。其他情况则是设置相同大小和位置(left,top),覆盖其父元素。进行擦除和显示时,也采用 JS,自己可以在下面的代码中添加隐藏后执行的函数。\nvar divList = document.querySelectorAll(\".scratcher\");\nfunction eraseAndShow(num) { // 这里是第 num 个覆盖元素\n divList[num].style.display = \"block\";\n for(let i = 1; i <= 19; i++) {\n setTimeout(() => {\n divList[num].style.backgroundPosition = `0% ${i * -100}%`; // 更改背景位置\n }, i * 100);\n }\n // 在这里可以添加准备执行的函数,使用 `setTimeout` 设置延时为 1900 毫秒。\n // 例如:\n // setTimeout(() => {myFunction();}, 1900);\n // 或者在第二行代码处添加参数,传递要执行的函数。\n for(let i = 1; i <= 19; i++) {\n setTimeout(() => {\n divList[num].style.backgroundPosition = `-100% ${i * -100}%`;\n }, i * 100 + 2100);\n }\n setTimeout(() => {divList[num].style.display = \"none\";}, 4000);\n}\n\n在添加以上所有代码后,就可以在任意一个元素内添加 <div class="scratcher"></div>,再在执行 JavaScript 代码 eraseAndShow(0),试验性地查看效果。\n示例下面是一些示例,点击按钮“隐藏和显示”可以看到效果\n\n .scratcher {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: url(https://s2.loli.net/2023/12/16/NOVHCgALzK4Fd1Q.png);\n background-size: 200% 2000%;\n display: none;\n z-index: 200;\n }\n\n\n以下文字在点击按钮后会被更改:\n\n 心情,是一种感情状态,拥有了好心情,也就拥有了自信,继而拥有了年轻和健康。就拥有了对未来生活的向往,充满期待,让我们拥有一份好心情吧,因为生活着就是幸运和快乐。\n 当你孤独时,风儿就是我的歌声,愿它能使你得到片刻的安慰;当你骄傲时,雨点就是我的警钟,愿它能使你获得永恒的谦逊。\n 友情如水,淡而长远;友情如茶,香而清纯;友情如酒,烈而沁心;友情如雨,细而连绵;友情如雪,松而亮洁。人生短暂,珍惜友情。\n \n 隐藏和显示\n \n\n\n由于是通过背景图片的位置模拟擦除效果,所以当点击按钮后在消失前按钮无法被点击。这个特性很好地使用在切换容器内容上。\n以下是一个简单的示例:\ndanger #d9534f\ntesttesttest\ntesttesttest\ntesttesttest\n隐藏和显示\n\n\n\n var divList = document.querySelectorAll(\".scratcher\");\n for(let i = 0; i < divList.length; i++) {\n divList[i].style.display = \"none\";\n divList[i].style.backgroundPosition = \"-100% -1900%\";\n var lpnt = divList[i].parentNode;\n if(window.getComputedStyle(lpnt).position == \"relative\") {\n divList[i].style.bottom = \"0\";\n divList[i].style.right = \"0\";\n }\n else {\n divList[i].style.top = String(lpnt.offsetTop) + \"px\";\n divList[i].style.left = String(lpnt.offsetLeft) + \"px\";\n }\n divList[i].style.width = String(lpnt.offsetWidth) + \"px\";\n divList[i].style.height = String(lpnt.offsetHeight) + \"px\";\n }\n function eraseAndShow(num, a) {\n divList[num].style.display = \"block\";\n for(let i = 1; i {\n divList[num].style.backgroundPosition = `0% ${i * -100}%`;\n }, i * 100);\n }\n if(num == 0) {\n if(a) {\n setTimeout(() => {\n document.querySelectorAll(\"div#theFirstExmDiv p\")[0].innerHTML = \"112 files changed, 3471 insertions(+), 2065 deletions(-)\";\n document.querySelectorAll(\"div#theFirstExmDiv p\")[1].innerHTML = \"Enumerating objects: 425, done.Counting objects: 100% (425/425), done.\";\n document.querySelectorAll(\"div#theFirstExmDiv p\")[2].innerHTML = \"Writing objects: 100% (227/227), 1.44 MiB | 875.00 KiB/s, done.Total 227 (delta 111), reused 0 (delta 0), pack-reused 0\";\n document.querySelector(\"div#theFirstExmDiv a.btn\").innerHTML = \"重新演示\";\n document.querySelector(\"div#theFirstExmDiv a.btn\").href = \"javascript:eraseAndShow(0, 0);\";\n }, 1900);\n }\n else {\n setTimeout(() => {\n document.querySelectorAll(\"div#theFirstExmDiv p\")[0].innerHTML = \"心情,是一种感情状态,拥有了好心情,也就拥有了自信,继而拥有了年轻和健康。就拥有了对未来生活的向往,充满期待,让我们拥有一份好心情吧,因为生活着就是幸运和快乐。\";\n document.querySelectorAll(\"div#theFirstExmDiv p\")[1].innerHTML = \"当你孤独时,风儿就是我的歌声,愿它能使你得到片刻的安慰;当你骄傲时,雨点就是我的警钟,愿它能使你获得永恒的谦逊。\";\n document.querySelectorAll(\"div#theFirstExmDiv p\")[2].innerHTML = \"友情如水,淡而长远;友情如茶,香而清纯;友情如酒,烈而沁心;友情如雨,细而连绵;友情如雪,松而亮洁。人生短暂,珍惜友情。\";\n document.querySelector(\"div#theFirstExmDiv a.btn\").innerHTML = \"隐藏和显示\";\n document.querySelector(\"div#theFirstExmDiv a.btn\").href = \"javascript:eraseAndShow(0, 1);\";\n }, 1900);\n }\n }\n for(let i = 1; i {\n divList[num].style.backgroundPosition = `-100% ${i * -100}%`;\n }, i * 100 + 2100);\n }\n setTimeout(() => {divList[num].style.display = \"none\";}, 4000);\n }\n\n\n\n\n希望对自己,对别人都有帮助!\n\n","categories":["Programming"],"tags":["JavaScript","HTML"]},{"title":"后缀表达式","url":"//posts/expres/","content":"基本的定义后缀表达式,也叫逆波兰表达式,指的就是将运算符置于运算数之后。前缀表达式亦然。平时使用的是中缀表达式。其实,也就是将表达式表示成表达式树。前、中、后缀表达式分别是这个树的前、中、后序遍历。由于后缀表达式运算的顺序就是从左往右,所以它不需要括号。\n\n\n转换例如 $14 + (1 + 2) \\times (7 - (6 \\div 2))$,表示为:\n\n使用后序遍历即表示为 $\\mathtt{14 \\ \\ 1 \\ \\ 2 \\ \\ + \\ \\ 7 \\ \\ 6 \\ \\ 2 \\ \\ \\div \\ \\ - \\ \\ \\times \\ \\ +}$,这就是它的后缀表达式。它们的结果都是 $26$。\n实现计算演示后缀表达式的模拟用栈实现,仅维护数字栈。其实就是把每一个数字压进栈,遇到符号时,就将栈顶两个元素弹出进行运算,再把结果重新压进栈。例如当栈顶元素是 $s_1$,弹出 $s_1$ 后栈顶元素是 $s_2$。遇到符号减号,将两个元素弹出,加入 $s_2 - s_1$\n其实就是:\n\\begin{array}{|c|}\n\\hdashline\ns_1 \\\\\n\\hline\ns_2 \\\\\n\\hline\n\\end{array}\n\n\\to\n\n\\begin{array}{|c|}\n\\hdashline\ns_2 - s_1 \\\\\n\\hline\n\\end{array}\n\n例如上面表达式树的栈的演示:\n\\begin{array}{c}\n{\\color{#3969f5}\\texttt{+}} \\\\\n\\begin{array}{|c|}\n\\hdashline\n{\\color{#3969f5}2} \\\\\n\\hline\n{\\color{#3969f5}1} \\\\\n\\hline\n14 \\\\\n\\hline\n\\end{array}\n\\end{array}\n\n\\to\n\n\\begin{array}{c}\n{\\color{#3969f5}\\texttt{/}} \\\\\n\\begin{array}{|c|}\n\\hdashline\n{\\color{#3969f5}2} \\\\\n\\hline\n{\\color{#3969f5}6} \\\\\n\\hline\n7 \\\\\n\\hline\n{\\color{#5cb85c}3} \\\\\n\\hline\n14 \\\\\n\\hline\n\\end{array}\n\\end{array}\n\n\\to\n\n\\begin{array}{c}\n{\\color{#3969f5}\\texttt{-}} \\\\\n\\begin{array}{|c|}\n\\hdashline\n{\\color{#5cb85c}3} \\\\\n\\hline\n{\\color{#3969f5}7} \\\\\n\\hline\n3 \\\\\n\\hline\n14 \\\\\n\\hline\n\\end{array}\n\\end{array}\n\n\\to\n\n\\begin{array}{c}\n{\\color{#3969f5}\\texttt{*}} \\\\\n\\begin{array}{|c|}\n\\hdashline\n{\\color{#5cb85c}4} \\\\\n\\hline\n{\\color{#3969f5}3} \\\\\n\\hline\n14 \\\\\n\\hline\n\\end{array}\n\\end{array}\n\n\\to\n\n\\begin{array}{c}\n{\\color{#3969f5}\\texttt{+}} \\\\\n\\begin{array}{|c|}\n\\hdashline\n{\\color{#5cb85c}12} \\\\\n\\hline\n{\\color{#3969f5}14} \\\\\n\\hline\n\\end{array}\n\\end{array}\n\n\\to\n\n\\begin{array}{|c|}\n\\hdashline\n{\\color{5cb85c}26} \\\\\n\\hline\n\\end{array}\n\n代码洛谷 P1449和上面的栈式一样的。\n#include <cstdio>\n#include <stack>\n#include <cstdlib>\nusing namespace std;\n\nint n, m;\nstack<int> stk;\nchar s[53];\nint main()\n{\n scanf(\"%s\", &s);\n \n char ts[53];\n int cnt = 0;\n for(int i = 0; s[i] != '\\0'; i++)\n {\n if(s[i] == '@') break;\n\n if(s[i] == '.')\n {\n int temp = atoi(ts); // 将记录的所有数字字符转化为数字\n // printf(\"temp:%d\\n\", temp);\n stk.push(temp);\n for(int i = 0; i <= cnt; i++){ts[i] = ' ';}\n cnt = 0;\n }\n else if(s[i] >= '0' && s[i] <= '9') ts[cnt++] = s[i];\n else\n {\n int temp1 = stk.top();\n stk.pop();\n int temp2 = stk.top();\n stk.pop();\n // printf(\"t1:%d t2:%d op:%c\\n\", temp2, temp1, s[i]);\n switch(s[i])\n {\n case '+':\n stk.push(temp2 + temp1);\n break;\n case '-':\n stk.push(temp2 - temp1);\n break;\n case '*':\n stk.push(temp2 * temp1);\n break;\n case '/':\n stk.push(temp2 / temp1);\n break;\n }\n }\n }\n\n printf(\"%d\", stk.top());\n\n return 0;\n}","categories":["CourseNotes"],"tags":["数学"]},{"title":"函数","url":"//posts/function/","content":"函数的作用一般来说,我都是懂的。函数的总用比较简单:\n\n优化代码量\n让程序代码更加清晰明了\n调用时更加方便\n\n总之,函数的存在就是为了更加方便,清晰,快速。\n\n\n定义函数方法:\n[函数类型] [函数名称]([参数])\n{\n [主体] ;\n return [] ;\n}\n\n如:\nint add(int x, int y)\n{\n int ans = x + y ;\n return ans ;\n}\n\n\n\n注意:当不返回(无return)时,函数类型应为void(表面含义无类型)。\n形参和实参当你运行这段代码时:\n#include <stdio.h>\nusing namespace std ;\n\nint swap(int x, int y)\n{\n int t = x ;\n x = y ;\n y = t ;\n}\n\nint main()\n{\n int a, b ;\n scanf(\"%d %d\", &a, &b) ;\n \n swap(a, b) ;\n printf(\"%d %d\\n\", a, b) ;\n}\n\n你会发现a还是a,b还是b。\n这是因为swap(int, int)只是把函数内的x和y交换了而已,a和b没有交换。\n因为x和y对于*main函数*来说只是形参,x和y只是拷了一份a和b。\n如果想交换a和b需要这样写:\n#include <stdio.h>\nusing namespace std ;\n\nint swap(int &x, int &y)\n{\n int t = x ;\n x = y ;\n y = t ;\n}\n\nint main()\n{\n int a, b ;\n scanf(\"%d %d\", &a, &b) ;\n \n swap(a, b) ;\n printf(\"%d %d\\n\", a, b) ;\n}\n\n此时此刻,你使用x和y就相当于引用了a和b。\n函数重载函数名不可重复。\n但是有几种方法可以重复:\n\n函数参数类型不同\n函数参数数量不同\n\n比如:\n#include <stdio.h>\nusing namespace std ;\n\nint add(int a, int b)\n{\n return a + b ;\n}\nvoid add()\n{\n printf(\"Hello\\n\") ;\n}\n\nint main()\n{\n int n = add(1, 2) ;\n printf(\"%d\\n\", n) ;\n add() ;\n \n return 0 ;\n}\n\n输出:\n3\nHello\n\n\n\n注意:仅仅函数类型不同不足以区分两个函数!\n拓展:主函数中的argc和argv 实际上我不懂用法:\nint main(int argc, char *argv[])\n\n或:\nint main(int argc, char **argv)\n\n\n\n含义:\n\nargc:是argument count 的缩写,保存运行时传递给main函数的参数个数。\nargv:是argument vector 的缩写,保存运行时传递main函数的参数,类型是一个字符指针数组,每个元素是一个字符指针,指向一个命令行参数。\n\n比如:\n#include <stdio.h>\nusing namespace std ;\n\nint main(int argc, char *argv[])\n{\n\tprintf(\"sum: %d\\n\", argc) ;\n\tfor(int i = 0; i < argc; i ++)\n\t{\n\t\tprintf(\"argc[%d], %s\\n\", i, argv[i]) ;\n\t}\n\t\n\treturn 0 ;\n}\n\n打开命令行,cd文件所在文件夹,输入:[file name].[file extension] hello world i am so happy\n结果为:\nsum: 7\nargc[0], 未命名1.exe\nargc[1], hello\nargc[2], world\nargc[3], i\nargc[4], am\nargc[5], so\nargc[6], happy\n\n这就是main函数参数作用。\n","categories":["CourseNotes"],"tags":["语言入门","函数,参数"]},{"title":"Git 的连接 Github 小记","url":"//posts/git-github/","content":"又是一个随记,方便自己使用的。首先,得到 官网下载,随后测试一下:\n$ git -v\ngit version (VERSION)\n\n就下载好了。\n\n使用 SSH 连接 Github首先确保拥有一个 Github 账号,打开终端,生成 SSH 密钥:\n$ ssh-keygen -t rsa -C \"email\"\n\n它的提示全部回车就可以了。”email” 是 Github 注册使用的邮箱地址。\n成功后会在用户文件夹(Windows 下通常是 %USERPROFILE% 环境变量,Linux 直接打开 ~/)下生成一个 .ssh 文件夹,打开 id_rsa.pub 文件,复制里面的密钥后回到 Github 打开设置,找到 “SSH anf PGP keys“ 一栏,点击 “New SSH key”,Title 填上,将刚刚复制的密钥粘贴到 “Key” 一栏,点击 “Add SSH key” 保存。\n\n随后可以验证是否完成,打开终端输入:\n$ ssh -T [email protected]\nThe authenticity of host 'github.com (IP ADDRESS)' can't be established.\nRSA key fingerprint is (FINGERPRINT).\nAre you sure you want to continue connecting (yes/no)? yes #在这里输入 yes\nHi (USER NAME)! You've successfully authenticated, but GitHub does not provide shell access. #连接成功\n\n连接 Github 仓库新建 Github 仓库。在电脑新建一个文件夹,创建一些文件,然后打开终端:\n$ git init\nInitialized empty Git repository in /.git/\n\n$ git add (FILE NAME) #你可以不断 add,也可以直接 git add .\ncreate mode 100644 (FILE NAME)\n\n$ git commit -m \"The commit information\" #建议 commit 信息用英文写详细,养成好习惯\n\n$ git branch -M main #现在的 Github 默认为 main 分支\n\n$ git remote add origin [email protected]:(USER NAME)/(REPOSITORY NAME).git #改成自己的用户名和仓库名\n\n$ git push -u origin main\nEnumerating objects: 7735, done.\nCounting objects: 100% (7735/7735), done.\nDelta compression using up to 4 threads\nCompressing objects: 100% (7413/7413), done.\nWriting objects: 100% (7735/7735), 55.74 MiB | 1.53 MiB/s, done.\nTotal 7735 (delta 2030), reused 0 (delta 0), pack-reused 0 \nremote: Resolving deltas: 100% (2030/2030), done.\nTo github.com:(USER NAME)/(REPOSITORY NAME).git\n * [new branch] main -> main\nbranch 'main' set up to track 'origin/main'.\n\n打开 Github,可以看到 Commit 记录和提交的文件。\n","categories":["Programming"],"tags":["Git"]},{"title":"使用 gdb 调试代码","url":"//posts/gdb-debug-file/","content":"这几天刚去学习了一下用 gdb 调试代码,在这儿记下来。\n首先,编译代码的时候需要加上 -g 选项,说明要加上调试信息,这样才可以正常调试。例如:\n$ g++ -g oi.cpp -o oi.exe\n\n随后,即可使用 gdb 打开文件进行调试。直接使用 gdb [file name] 即可。\n$ gdb oi\nGNU gdb (GDB) 7.8.1\nCopyright (C) 2014 Free Software Foundation, Inc.\nLicense GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n# ...[很多信息]\nFor help, type \"help\".\nType \"apropos word\" to search for commands related to \"word\"...\nReading symbols from oi...done. # 成功信息\n(gdb) # 现在可以键入调试命令了\n\n\n\ngdb 一些常用调试命令(命令缩写)的详细解释:\n代码、路径list命令缩写是 l。可以查看代码,后面跟上数字说明要查看第几行附近的代码,或者跟上函数名说明要查看这个函数附近的代码。若没有参数则继续从上一次最后显示的那一行显示下去。\n例如:\n(gdb) l 17\n12 for(int i = 0; s[i] != '\\0'; i++)\n13 {\n14 char l, r;\n15 if((s[i] >= 'a' && s[i] <= 'z') || (s[i] >= '0' && s[i] <= '9'))\n16 {\n17 l = s[i]; # 这是代码外的注释:行显示在中间。\n18 ans[cnt++] = s[i];\n19 }\n20 if(s[i] == '-')\n21 {\n(gdb) list main\n3\n4 int p1, p2, p3, cnt;\n5 char s[200];\n6 char ans[7000];\n7 int main()\n8 {\n9 scanf(\"%d %d %d\\n\", &p1, &p2, &p3);\n10 scanf(\"%s\", &s);\n11\n12 for(int i = 0; s[i] != '\\0'; i++)\n(gdb) l # 继续显示\n13 {\n14 char l, r;\n15 if((s[i] >= 'a' && s[i] <= 'z') || (s[i] >= '0' && s[i] <= '9'))\n16 {\n17 l = s[i];\n18 ans[cnt++] = s[i];\n19 }\n20 if(s[i] == '-')\n21 {\n22 r = s[i + 1];\n\ninfo source可以简写为 i source获取代码信息,可以查看 gdb 获取的代码路径是否正确。\n例如:\n(gdb) info source\nCurrent source file is oi.cpp\nCompilation directory is D:\\MyCode\nLocated in D:\\MyCode\\oi.cpp\nContains 65 lines.\nSource language is c++.\nCompiled with DWARF 2 debugging format.\nDoes not include preprocessor macro info.\n\nfile参数是文件路径,可以重新打开一个文件调试。例如:\n(gdb) file D:\\\\MyCode\\\\oi\nReading symbols from D:\\MyCode\\oi...done.\n\ncd同任何命令行的 cd 命令一样,切换工作文件夹。\n程序运行时run 命令。命令缩写是 r。运行程序,直至遇到断点或程序结束。\nprint 命令。命令缩写是 p。在程序运行时输出变量(数组)的值。\nbreak 命令。命令缩写是 b,参数是行号或函数名。表示在函数或第几行设置断点。例如:\n(gdb) break main\nBreakpoint 1 at 0x40153d: file oi.cpp, line 9.\n(gdb) b 43\nBreakpoint 2 at 0x401722: file oi.cpp, line 43.\n\ncontinue 命令。命令缩写是 c。遇到断点后使用,继续执行,运行到下一个断点或程序结束。\ndelete 命令。命令缩写是 d。删除断点,参数是断点编号。就是 Breakpoint 1 at []: file [], line []. Breakpoint 后面的数字。\nnext 命令。命令缩写是 n。执行当前行语句,如果当前行有函数调用,则将其视为一个整体执行。\n熟知以上这些,就可以简单地调试代码了。一个实例:\n(gdb) break 25\nBreakpoint 1 at 0x4016b1: file oi.cpp, line 25.\n(gdb) break 32\nBreakpoint 2 at 0x4016ea: file oi.cpp, line 32.\n(gdb) break 35\nBreakpoint 3 at 0x4016fe: file oi.cpp, line 35.\n(gdb) run\nStarting program: D:\\MyCode\\oi.exe\n[New Thread 37568.0x97c8]\n[New Thread 37568.0x25fc]\n2 3 2\na-d-d\n\nBreakpoint 2, main () at oi.cpp:33\n33 l = l - 'a' + 'A';\n(gdb) print p1\n$2 = 2\n(gdb) print p2\n$3 = 3\n(gdb) p p3\n$4 = 2\n(gdb) c\nContinuing.\n\nBreakpoint 3, main () at oi.cpp:37\n37 if(p3 == 1)\n(gdb) print l\n$5 = 65 'A'\n(gdb) print r\n$6 = 68 'D'\n(gdb) continue\nContinuing.\n\nBreakpoint 1, main () at oi.cpp:25\n25 ans[cnt++] = '-';\n(gdb) print ans[cnt - 1]\n$7 = 100 'd'\n(gdb) print ans\n$8 = \"aCCCBBBd\", '\\000' <repeats 6991 times>\n(gdb) continue\nContinuing.\naCCCBBBd-d\n[Thread 37568.0x25fc exited with code 0]\n[Inferior 1 (process 37568) exited normally] # 程序结束\n\n获取信息获取信息通常使用 info 命令。就介绍常用的那些。\ninfo breakpoint可以简写为 i b,查看断点信息。\ninfo registers可以简写为 i reg,查看寄存器信息。\ninfo stack可以简写为 i s,查看堆栈使用,在递归的时候很有效。\n实例:\n(gdb) info breakpoint\nNum Type Disp Enb Address What\n1 breakpoint keep y 0x000000000040153b in dfs(int) at oi.cpp:10\n breakpoint already hit 5 times\n2 hw watchpoint keep y x\n(gdb) info reg\nrax 0x4 4\nrbx 0x1 1\nrcx 0x4 4\nrdx 0x3 3\nrsi 0x11 17\nrdi 0xc41440 12850240\nrbp 0x6cfcf0 0x6cfcf0\nrsp 0x6cfcc0 0x6cfcc0\nr8 0xc43d10 12860688\nr9 0x1 1\nr10 0xc40000 12845056\nr11 0x6ceac0 7137984\nr12 0x1 1\nr13 0x8 8\nr14 0x0 0\nr15 0x0 0\nrip 0x40153b 0x40153b <dfs(int)+11>\neflags 0x206 [ PF IF ]\ncs 0x33 51\nss 0x2b 43\nds 0x0 0 \nes 0x0 0 \nfs 0x0 0 \ngs 0x0 0\n(gdb) info stack\n#0 dfs (x=10) at oi.cpp:10\n#1 0x00000000004015b5 in dfs (x=9) at oi.cpp:20\n#2 0x00000000004015b5 in dfs (x=8) at oi.cpp:20\n#3 0x00000000004015b5 in dfs (x=7) at oi.cpp:20\n#4 0x00000000004015b5 in dfs (x=6) at oi.cpp:20\n#5 0x00000000004015b5 in dfs (x=5) at oi.cpp:20\n#6 0x00000000004015b5 in dfs (x=4) at oi.cpp:20\n#7 0x00000000004015b5 in dfs (x=3) at oi.cpp:20\n#8 0x00000000004015b5 in dfs (x=2) at oi.cpp:20\n#9 0x00000000004015b5 in dfs (x=1) at oi.cpp:20\n#10 0x00000000004015b5 in dfs (x=0) at oi.cpp:20\n#11 0x000000000040163c in main () at oi.cpp:30\n\n其他shell 命令。可以执行终端命令。\nquit 命令。简写为 q。退出 gdb 调试。\n","categories":["Programming"],"tags":["编译"]},{"title":"普通图的储存和遍历","url":"//posts/graph-search/","content":"其实之前也写过关于图的储存的文章,但是没写全,也没有写代码。在这里把最近复习的重新补上来。这里只讲了三种储存:邻接矩阵、邻接表、链式前向星,对于遍历,只记录写法较简单的邻接表。\n\n\n储存题目:洛谷 B3643 图的存储\n邻接表由于这个方法是比较常用的,把它提到前面。\n用一个动态数组(通常是 STL 提供的 std::vector)存储每一个结点的出边。二维数组 e 中,e[i] 中的每一个元素 j 代表结点有一条从 i 到 j 的边。对于无向图,若 u, v 存在边,则将 v 加入 e[u],将 u 加入 e[v]。对于路径权值,通常使用结构体(包含两个变量)。分别代表该出边的权值和连接到的结点。\n示例:\nvector<int> e[1003];\nint main()\n{\n scanf(\"%d %d\", &n, &m); // n 代表该图的结点数,m 是该图的边数\n \n for(int i = 1; i <= m; i++)\n {\n int u, v; // 输入 u, v 表示 u 和 v 之间有一条边\n scanf(\"%d %d\", &u, &v);\n e[u].push_back(v);\n e[v].push_back(u); // 这里是无向图的写法\n }\n}\n// 以下关于“储存”代码示例的输入也按照上面的格式\n\n邻接表的空间、时间复杂度都比较优异,平常一般使用它。下面所写的遍历就使用了邻接表\n邻接矩阵(一般不用)用一个二维的布尔数组存储。数组 a 中,a[i][j] 如果为 1 则代表着编号为 i 的结点与编号为 j 的结点之间存在一条从 i 到 j 的边。对于无向图,若 u, v 间存在边,直接使 a[u][v] 和 a[v][u] 同时为 1 即可(代表着 u 和 v 之间有一条双向边)。\n示例:\nbool a[1003][1003];\nint main()\n{\n scanf(\"%d %d\", &n, &m);\n \n for(int i = 1; i <= m; i++)\n {\n int u, v;\n scanf(\"%d %d\", &u, &v);\n a[u][v] = 1;\n a[v][u] = 1;\n }\n}\n\n使用它存图,时间复杂度(遍历整张图)和空间复杂度自然都很大。一般只会在边数接近点数平方的图(稠密图)上使用。\n链式前向星实际上就是用链表重现邻接表。详见代码:\nstruct node\n{\n int to, next; // to: 边的终点 next: 下一条边\n};\nconst int TEMP = 2e6 + 100;\nint head[TEMP], cnt = 0; // head[i]: 以 i 为起点的第一条边的编号 cnt: 当前边的编号\nnode tree[TEMP]; // 这里是本体数组\n\nvoid add(int a, int b)\n{\n ++cnt;\n tree[cnt].to = b;\n tree[cnt].next = head[a];\n head[a] = cnt;\n}\n\nint n, m;\nint main()\n{\n scanf(\"%d\", &n, &m);\n for(int i = 1; i <= m; i++)\n {\n int u, v;\n scanf(\"%d %d\", &u, &v);\n add(u, v);\n add(v, u);\n }\n}\n\n\n链式前向星主要用于边比较多,顶点比较少的情况链式前向星的优点:比邻接表还省空间,可以解决某些卡空间的问题,删除边也很方便,只需要更改next指针的指向即可。根据图的疏密选择存储方式,一般情况下用邻接表,卡空间时间这些要求比较高的题目或者需要删除边操作的用链式前向星。https://www.acwing.com/blog/content/6994/\n\n遍历以下的遍历都使用邻接表。以有向图为例。\n直接使用代码吧,解释都在注释了。\nDFSBFS遍历了从结点编号为 1 到最后一个结点(编号为 n)的所有路径,\n// 有向图(不保证无环)\nint n; // 结点个数\nvector<int> e[1503]; // e: 一个图的邻接表\nbool flag[1503]; // 记录该结点(同一条路径)是否访问过\n// 递归实现\nvoid dfs(int now) // now: 当前结点\n{\n if(now == n) return; // 遍历到了最后一个结点\n flag[now] = 1; // 防止重复走一个环\n for(int i = 0; i < e[now].size(); i++)\n {\n if(!flag[now]) dfs(e[now][i]);\n }\n flag[now] = 0; // 搜索回溯,若只要遍历一次结点,请把这行代码注释掉\n}\n\n所有的搜索遍历都大同小异。遍历了所有结点\n// 有向图(不保证无环)\nint n; // 结点个数\nvector<int> e[1503]; // 一个图的邻接表\nbool flag[1503]; // 记录该结点(同一条路径)是否访问过\n\nqueue<int> q;\nfor(int i = 0; i < e[1].size(); i++)\n{\n q.push(e[1][i]); // 从结点 1 开始访问,就先把它连向的所有加入队列\n}\nwhile(!q.empty()) {\n int now = q.front();\n q.pop();\n for(int i = 0; i < e[now].size(); i++)\n {\n if(!flag[e[now]]) // 没有访问过\n {\n q.push(e[now]);\n flag[e[now]] = 1;\n }\n }\n}\n\n一个例题:洛谷 P1807 最长路。简述:n 个顶点,m 条边的带权有向无环图,各结点编号为 1 到 n。求从 1 到 n 的最长路径,输出最大权值。若无法从 1 到达 n 输出 -1。输入:n(1<=n<=1500), m(0<=m<=5e4)。接下来 m 行,每行三个整数 u, v, w(-1e5<=w<=1e5),表示有一条从 u 到 v 的有向边,边权为 w。\n解题(DFS):\n#include <cstdio>\n#include <vector>\nusing namespace std;\n\nstruct node {\n int next, weight; // next: 该有向边指向的结点 wei: 该有向边的权值\n};\n\n // /=> 设置为负数是因为防止负权值\nint n, m, maxw = -0x7fffffff, flag[1503]; // maxw: 最大权值(从 1 到 n 的最长路权值) flag[i]: 从 1 到 i 的最大权值\nbool isvis; // 是否访问到第 n 个结点\nvector<node> e[1503]; // 邻接表\n\nvoid dfs(int now, int wei) // now: 当前访问到的结点 wei: 当前走到这个结点的权值\n{\n if(now == n) // 访问到第 n 个结点\n {\n isvis = 1;\n maxw = (wei > maxw) ? (wei) : (maxw); // 将最大权值替换成本次所得结果\n return;\n }\n // /=> 剪枝\n if(flag[now] != 0 && flag[now] >= wei) return; // 上次的答案比本次运算结果要大,本次结果一定偏小,直接退出\n flag[now] = wei; // 记录本次(更大)的答案\n\n for(int i = 0; i < e[now].size(); i++)\n {\n dfs(e[now][i].next, wei + e[now][i].weight); // 按次序遍历当前结点的下一个结点\n }\n}\n\nint main()\n{\n scanf(\"%d %d\", &n, &m); // 存图开始(邻接表)\n for(int i = 1; i <= m; i++)\n {\n int u, v, w;\n node data;\n scanf(\"%d %d %d\", &u, &v, &w);\n data.next = v;\n data.weight = w;\n e[u].push_back(data);\n } // 存图结束\n\n dfs(1, 0); // 从 1 开始访问,最初的权值为 0\n if(isvis) // 到达过第 n 个结点\n {\n printf(\"%d\\n\", maxw);\n return 0;\n }\n printf(\"-1\\n\"); // 没有到达\n\n return 0;\n}","categories":["CourseNotes"],"tags":["搜索","树"]},{"title":"数据结构:树的概念与储存","url":"//posts/graph-tree/","content":"树也是一种数据结构,它是非线性数据结构,它能很好地描述一个数据集合中的分支和层次,是一个比较重要的课题,以后的搜索和竞赛都有可能要用到它。树形结构的应用非常广泛,什么索引、语法结构。虽说概念比较繁琐 老师讲了一个小时。有点让人头疼(我咕了好多篇笔记了)。\n树的概念前面前置芝士,简单说一下这些奇怪的名称:\n\n树中的每一个元素称为结点 node。\n两个结点之间的线称为边 edge。\n通过几条边,这几条边组成路径。\n\n\n\n树的结点一般画为圆圈,树若画出来也就是几个结点(圆)和几条边(线)组成的图,比较形象化,实际的树的储存方式是通过链表中的数据域将一个个结点(元素)连接起来,比较麻烦,后面再说。还有一个概念:树的边的个数比树的结点的个数少一。也就是说,假设树的结点的个数为 $n$,则树的边的个数为 $n-1$。\n判断一张图是否为树树最重要的就是每两个结点之间有且只有一条路径可以到达,也就是说,不可以形成环,不可以在一个树中无法到达所有结点。例如,下面就是一个树:下面两张图不是树,他们分别违反了“不可以形成环”、“不可以在一个树中无法到达所有结点”。\n更多的概念首先放一张图:\n以这张图为例,来说下面的概念吧。根结点 root:根结点通常在最上方,是所有子结点的父结点。在上面的那张图中,结点 1 就是整张图的根结点。在上面的图中,根结点可以更换,也不会影响到什么,但是根结点一变就会让树形态发生变化(假如结点 2 是整个树的根结点,那么树会变下面的图)。一个树是必须要有根结点的,根结点只有一个。父结点 parent(双亲结点) 子结点 child(孩子结点):一个结点的分支就是那个结点的子结点,相反那个结点是分支结点的父结点。子结点通过父结点到达。例如结点 4 5 是结点 2 的子结点,结点 3 是结点 6 7 的父结点。兄弟结点:同一个父结点的子结点称为兄弟结点。例如结点 4 5 互为兄弟结点,6 7 互为兄弟结点。度:树的度就是一个结点子结点的个数。例如结点 2 的度就是 2,因为它只有两个子结点。深度:从根结点到当前结点的层数,根结点的深度是 1。例如,结点 1 深度为 1,结点 2 3 深度为 2,结点 4 5 6 7 深度为 3。叶子结点:一个结点的度为 0,就叫做叶子结点。例如结点 4 5 6 7 都是叶子结点。n 叉树:在树中,这个数是多少叉看度最多的结点,例如这个树就是二叉树(也比较特殊,后面讲)。子树:假设将树中任意一个度不为 0 的结点与它的父结点切断它们之间的边,那么断开的那一部分又能成为一个新的树,称为子树。例如结点 2 4 5 可以组成一个子树。\n还有一大堆子子孙孙祖先的什么的,懒得写了。说真的,感觉说多了意义不大。\n二叉树的概念n 叉树中,又出现了一个二叉树 Binary Tree 这么个奇怪的概念。什么左子树右孩子什么的不记了,就讲三个我认为比较重要的。\n完美二叉树:也叫做满二叉树。简单来说就是一个深度为 $n$ 的二叉树,拥有 $2^n - 1$ 个结点。看着的话就是若再增加一个结点使其继续为二叉树,深度就必须要加一了。刚才的示例图就是一个完美二叉树。完全二叉树:完全二叉树的叶子结点可以不是满的,但是剩下的叶子结点必须都在图的左边。例如那张示例图若将结点 7 去掉,它就只是一个完全二叉树。完满二叉树:完满二叉树的结点除了叶子结点以外其他结点的度都必须是 2。示例图若将结点 4 5 去掉,它就只是一个完满二叉树。\n注意:\n这三个概念极易弄混淆,稍不注意就忘了。完美二叉树一定也是完全二叉树和完满二叉树,但完满二叉树不一定是完全二叉树和完美二叉树。(别说他晕,我也晕了)\n\n\n树的储存一大堆基础概念,已经够呛了(悲)。学到树的储存已经开始逐渐迷惑。。。一般来说,树也是不太可能用真正的指针链表来储存,毕竟太难写了,内存限制一般比时间限制够用一些,就用数组模拟链表。链表就是要关心指针域,下面就是一大堆奇奇怪怪的方法。\n可能用不到的备注:\n一般来说,这些方法用不太到,要么炸时间要么炸空间要么难实现。所以就按这种奇特的分类方法分类了,反正感觉用不到。这句话也兼下面。\n\n\n父亲表示法:顾名思义,指针域指向父结点。孩子表示法:指针域指向子结点。父亲孩子表示法:双向链表结构,也没啥用。\n上述缺点:\n很明显,父亲法若寻找一个子结点可能要遍历整个表,很耗时间;孩子法度一大肯定爆内存,因为将每个子结点都存了下来;父亲孩子更糟糕,内存更大了,没有意义。\n\n\n可能会用到的孩子兄弟表示法:适用于二叉树,也是一个双链表结构,一个结点连接其子结点和兄弟结点。邻接矩阵表示法:见这里。邻接表表示法:也看上面。\n\n\n假设根结点为 2 时的情况:\n\n\n","categories":["CourseNotes"],"tags":["基础算法","树","数据结构"]},{"title":"CSS 鼠标悬浮窗口效果","url":"//posts/hover-show/","content":"最近弄的 github 卡片,弄了半天弄出来的鼠标悬浮显示文字的效果。使用 CSS 伪元素弄出来的小提示。但是不适合 overflow: hidden; 的元素。不管怎么说,还是很好用的,忘掉了就不太好,也就放到博客里来了。\n\n\n\n\n\n代码\n效果\n\n\n\n<span aria-label="左边提示" balloon-shown="left">鼠标</span>\n鼠标\n\n\n<span aria-label="右边提示" balloon-shown="right">悬浮</span>\n悬浮\n\n\n<span aria-label="上边提示" balloon-shown="up">文字</span>\n文字\n\n\n<span aria-label="上边提示" balloon-shown="down">提示</span>\n提示\n\n\n\n [aria-label][balloon-shown] {\n position: relative;\n }\n [aria-label][balloon-shown=\"left\"]::before {\n border: 5px solid transparent;\n border-left-color: #202335;\n }\n [aria-label][balloon-shown=\"right\"]::before {\n border: 5px solid transparent;\n border-right-color: #202335;\n }\n [aria-label][balloon-shown=\"up\"]::before {\n border: 5px solid transparent;\n border-top-color: #202335;\n }\n [aria-label][balloon-shown=\"down\"]::before {\n border: 5px solid transparent;\n border-bottom-color: #202335;\n }\n [aria-label][balloon-shown]::before {\n width: 0;\n height: 0;\n /* pointer-events: none; */ /* 让鼠标无法悬浮在所弹出的伪元素上 */\n z-index: 10;\n content: \"\";\n position: absolute;\n opacity: 0;\n visibility: hidden;\n transition: opacity .4s, transform .4s, visibility .4s;\n }\n [aria-label][balloon-shown]::after {\n opacity: 0;\n z-index: 10;\n /* pointer-events: none; */ /* 让鼠标无法悬浮在所弹出的伪元素上 */\n visibility: hidden;\n background-color: #202335;\n content: attr(aria-label);\n white-space: nowrap;\n border-radius: 2px;\n position: absolute;\n padding: .5em 1em;\n transition: opacity .4s, transform .4s, visibility .4s;\n color: #eee;\n }\n [aria-label][balloon-shown]:hover::after, [aria-label][balloon-shown]:hover::before {\n opacity: 0.9;\n visibility: visible;\n }\n [aria-label][balloon-shown=\"left\"]::after {\n margin-right: 10px;\n }\n [aria-label][balloon-shown=\"left\"]::after, [aria-label][balloon-shown=\"left\"]::before {\n right: 100%;\n top: 50%;\n transform: translate(5px, -50%);\n }\n [aria-label][balloon-shown=\"right\"]::after {\n margin-left: 10px;\n }\n [aria-label][balloon-shown=\"right\"]::after, [aria-label][balloon-shown=\"right\"]::before {\n left: 100%;\n top: 50%;\n transform: translate(-5px, -50%);\n }\n [aria-label][balloon-shown=\"left\"]:hover::after, [aria-label][balloon-shown=\"left\"]:hover::before, [aria-label][balloon-shown=\"right\"]:hover::after, [aria-label][balloon-shown=\"right\"]:hover::before {\n transform: translate(0, -50%);\n }\n [aria-label][balloon-shown=\"up\"]::after {\n margin-bottom: 10px;\n }\n [aria-label][balloon-shown=\"up\"]::after, [aria-label][balloon-shown=\"up\"]::before {\n bottom: 100%;\n left: 50%;\n transform: translate(-50%, 5px);\n }\n [aria-label][balloon-shown=\"down\"]::after {\n margin-top: 10px;\n }\n [aria-label][balloon-shown=\"down\"]::after, [aria-label][balloon-shown=\"down\"]::before {\n left: 50%;\n top: 100%;\n transform: translate(-50%, -5px);\n }\n [aria-label][balloon-shown=\"up\"]:hover::after, [aria-label][balloon-shown=\"up\"]:hover::before, [aria-label][balloon-shown=\"down\"]:hover::after, [aria-label][balloon-shown=\"down\"]:hover::before {\n transform: translate(-50%, 0);\n }\n\n\n代码是这样的:\n[aria-label][balloon-shown] {\n position: relative;\n}\n[aria-label][balloon-shown=\"left\"]::before {\n border: 5px solid transparent;\n border-left-color: #202335;\n}\n[aria-label][balloon-shown=\"right\"]::before {\n border: 5px solid transparent;\n border-right-color: #202335;\n}\n[aria-label][balloon-shown=\"up\"]::before {\n border: 5px solid transparent;\n border-top-color: #202335;\n}\n[aria-label][balloon-shown=\"down\"]::before {\n border: 5px solid transparent;\n border-bottom-color: #202335;\n}\n[aria-label][balloon-shown]::before {\n width: 0;\n height: 0;\n /* pointer-events: none; */ /* 让鼠标无法悬浮在所弹出的伪元素上 */\n z-index: 10;\n content: \"\";\n position: absolute;\n opacity: 0;\n visibility: hidden;\n transition: opacity .4s, transform .4s, visibility .4s;\n}\n[aria-label][balloon-shown]::after {\n opacity: 0;\n z-index: 10;\n /* pointer-events: none; */ /* 让鼠标无法悬浮在所弹出的伪元素上 */\n visibility: hidden;\n background-color: #202335;\n content: attr(aria-label);\n white-space: nowrap;\n border-radius: 2px;\n position: absolute;\n padding: .5em 1em;\n transition: opacity .4s, transform .4s, visibility .4s;\n color: #eee;\n}\n[aria-label][balloon-shown]:hover::after, [aria-label][balloon-shown]:hover::before {\n opacity: 0.9;\n visibility: visible;\n}\n[aria-label][balloon-shown=\"left\"]::after {\n margin-right: 10px;\n}\n[aria-label][balloon-shown=\"left\"]::after, [aria-label][balloon-shown=\"left\"]::before {\n right: 100%;\n top: 50%;\n transform: translate(5px, -50%);\n}\n[aria-label][balloon-shown=\"right\"]::after {\n margin-left: 10px;\n}\n[aria-label][balloon-shown=\"right\"]::after, [aria-label][balloon-shown=\"right\"]::before {\n left: 100%;\n top: 50%;\n transform: translate(-5px, -50%);\n}\n[aria-label][balloon-shown=\"left\"]:hover::after, [aria-label][balloon-shown=\"left\"]:hover::before, [aria-label][balloon-shown=\"right\"]:hover::after, [aria-label][balloon-shown=\"right\"]:hover::before {\n transform: translate(0, -50%);\n}\n[aria-label][balloon-shown=\"up\"]::after {\n margin-bottom: 10px;\n}\n[aria-label][balloon-shown=\"up\"]::after, [aria-label][balloon-shown=\"up\"]::before {\n bottom: 100%;\n left: 50%;\n transform: translate(-50%, 5px);\n}\n[aria-label][balloon-shown=\"down\"]::after {\n margin-top: 10px;\n}\n[aria-label][balloon-shown=\"down\"]::after, [aria-label][balloon-shown=\"down\"]::before {\n left: 50%;\n top: 100%;\n transform: translate(-50%, -5px);\n}\n[aria-label][balloon-shown=\"up\"]:hover::after, [aria-label][balloon-shown=\"up\"]:hover::before, [aria-label][balloon-shown=\"down\"]:hover::after, [aria-label][balloon-shown=\"down\"]:hover::before {\n transform: translate(-50%, 0);\n}\n\n使用时这样使用:\n<标签 aria-label=\"文字\" balloon-shown=\"up\"或\"down\"或\"left\"或\"right\">文字</标签>\n\n","categories":["Programming"],"tags":["HTML","CSS"]},{"title":"Hexo 建立静态博客记录","url":"//posts/hexo-pretty/","content":"这篇文章主要是为了记录自己用 Hexo 建站(主题 NexT)的经过,方便他人查阅和自己以后用。关于 Hexo 的准备,可以看官方文档 ,关于主题 NexT 的,可以看 这里 \n如果你是的阅读目的是准备第一次使用 Hexo 搭博客,可以遵照本文提示看。如果你准备美化你的 Hexo(最好且主题为 NexT)的博客,可以跳到更多高阶美化\n操作系统的异同\n各种操作系统的过程基本一致。本文中的安装环境的主要做法是直接通过官网下载安装,其他下载方法也可行,这里不列举。\n\n\n\n\n准备下载、准备环境如果还没有安装 Git ,去官网下载。Hexo 部署需要用到。\nHexo 是基于 Node.js 开发的,若未安装,需要先安装 Node.js ,两个版本都可以。安装完毕后打开终端检查:\n$ node -v\nv18.16.0\n$ npm -v\n9.5.1\n$ npx -v\n9.5.1\n\n当三个命令都正常显示版本号时,就安装成功了。\n随后,下载 Hexo 包。执行以下命令:\n$ npm install -g hexo-cli\n\n$ hexo -v\nhexo-cli: 4.3.0\nos: ...\n# 出现一些依赖包的版本号表示安装成功\n\n随后,可以开始建立站点文件夹了。执行以下命令初始化 Hexo 站点:\n$ hexo init blog # 文件夹的名字,自己可更改,同下\n$ cd blog\n$ npm install\n\n此时,站点文件夹已新建完毕。目录大概是这样(...... 表示省略了很多文件):\n.\n├─ .github\n│ └─ dependabot.yml\n├─ node_modules\n│ ├─ .bin\n│ └─ ......\n├─ scaffolds\n│ ├─ draft.md\n│ ├─ page.md\n│ └─ post.md\n├─ source\n│ └─ _posts\n│ └─ hello-world.md\n├─ themes\n│ └─ .gitkeep\n├─ _config.landscape.yml\n├─ _config.yml\n├─ package-lock.json\n└─ package.json\n\n随后,可以在本地运行查看效果:\n$ hexo server\nINFO Validating config\nINFO Start processing\nINFO Hexo is running at http://localhost:4000/ . Press Ctrl+C to stop.\n\n端口占用\n如果出现 FATAL Port 4000 has been used. Try other port instead. 说明默认的 4000 端口被占用。逐一排查或解决:\n\n排查是否是 Hexo 已经运行了一个服务。(不同终端)解决:终端内按 Ctrl+C 终止那个已经运行的 Hexo 服务。\n排查有无其他应用占用端口解决:关闭占用端口的应用。\n以上两个方案都不可行。解决:运行命令 hexo s -p [number] 更改端口。,[number] 为 4000 以上的数字,例如:hexo s -p 8080\n\n\n\n访问 http://localhost:4000/ 查看效果。初始的欢迎页面大概是这个样子:\n出现问题\n如果以上步骤完成后出现无法访问的情况,可能是什么步骤出现了问题。若找不到原因,删除工作文件夹后以上步骤逐一排查,重试一遍以后一般都会正常。\n\n\n自此,完成了站点文件环境的准备。以下的步骤都在这个文件夹内进行,文中相对文件路径父目录为这个文件夹。\n站点整体设置打开站点文件夹中的 _config.yml,这个文件是 Hexo 站点的整体设置。初始时的内容大概是这个:\n# Hexo Configuration\n## Docs: https://hexo.io/docs/configuration.html\n## Source: https://github.com/hexojs/hexo/\n\n# Site\ntitle: Hexo\nsubtitle: ''\ndescription: ''\nkeywords:\nauthor: John Doe\nlanguage: en\ntimezone: ''\n\n# URL\n## Set your site url here. For example, if you use GitHub Page, set url as 'https://username.github.io/project'\nurl: http://example.com\npermalink: :year/:month/:day/:title/\npermalink_defaults:\npretty_urls:\n trailing_index: true # Set to false to remove trailing 'index.html' from permalinks\n trailing_html: true # Set to false to remove trailing '.html' from permalinks\n\n# Directory\nsource_dir: source\npublic_dir: public\ntag_dir: tags\narchive_dir: archives\ncategory_dir: categories\ncode_dir: downloads/code\ni18n_dir: :lang\nskip_render:\n\n# Writing\nnew_post_name: :title.md # File name of new posts\ndefault_layout: post\ntitlecase: false # Transform title into titlecase\nexternal_link:\n enable: true # Open external links in new tab\n field: site # Apply to the whole site\n exclude: ''\nfilename_case: 0\nrender_drafts: false\npost_asset_folder: false\nrelative_link: false\nfuture: true\nsyntax_highlighter: highlight.js\nhighlight:\n line_number: true\n auto_detect: false\n tab_replace: ''\n wrap: true\n hljs: false\nprismjs:\n preprocess: true\n line_number: true\n tab_replace: ''\n\n# Home page setting\n# path: Root path for your blogs index page. (default = '')\n# per_page: Posts displayed per page. (0 = disable pagination)\n# order_by: Posts order. (Order by date descending by default)\nindex_generator:\n path: ''\n per_page: 10\n order_by: -date\n\n# Category & Tag\ndefault_category: uncategorized\ncategory_map:\ntag_map:\n\n# Metadata elements\n## https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta\nmeta_generator: true\n\n# Date / Time format\n## Hexo uses Moment.js to parse and display date\n## You can customize the date format as defined in\n## http://momentjs.com/docs/#/displaying/format/\ndate_format: YYYY-MM-DD\ntime_format: HH:mm:ss\n## updated_option supports 'mtime', 'date', 'empty'\nupdated_option: 'mtime'\n\n# Pagination\n## Set per_page to 0 to disable pagination\nper_page: 10\npagination_dir: page\n\n# Include / Exclude file(s)\n## include:/exclude: options only apply to the 'source/' folder\ninclude:\nexclude:\nignore:\n\n# Extensions\n## Plugins: https://hexo.io/plugins/\n## Themes: https://hexo.io/themes/\ntheme: landscape\n\n# Deployment\n## Docs: https://hexo.io/docs/one-command-deployment\ndeploy:\n type: ''\n\n一些刚建站会用到的配置的解释:\n\ntitle: 网站标题。显示在 HTML <title></title> 和网站标题位置。\nsubtitle: 网站副标题。显示在网站标题下面,小字。\nauthor: 网站所有者。通常显示在网站底部版权的地方。\n\n其他的以后慢慢会用到。先把上面三个站点的信息填好吧。\nYAML 格式\n对于第一次使用 YAML 的很容易漏掉选项后的空格。正确:theme: landscape错误:theme:landscape\n\n\nHexo 命令没有太多好说的。就在这里列举。\n\n\n\n命令\n缩写\n解释\n\n\n\nhexo init <folder>\nhexo i\n新建一个站点文件夹 <folder>,Hexo 会通过 Git clone hexo starter, hexo-theme-scape 并通过 NPM 下载部分依赖。\n\n\nhexo list <type>\nhexo l <type>\n在命令行获取站点数据。<type> 值为 page post route tag category,例如 hexo l post 列出所有文章。\n\n\nhexo new <post>\nhexo n <post>\n写一篇新文章,让 Hexo 在 ./source/_posts/ 下生成名为 <post>.md 的文件。\n\n\nhexo generate\nhexo g\n让 Hexo 生成站点文件。\n\n\nhexo server\nhexo s\n在本地运行网站,查看效果。\n\n\nhexo clean\n\n清除生成的网页。\n\n\nhexo deploy\nhexo d\n部署站点。需要配置和依赖,下面会讲到。\n\n\n通常来说,写完文章后运行:\n$ hexo g\n$ hexo d # -m \"...\" # commit 信息,可选\n\n部署到 Github Pages博客自然需要进行部署,本地运行的博客别人看不到。我当时选择部署到 Github Pages 。所以,当时注册了 Github(若未注册,先注册 Github ),然后连接本地,参照Git 的连接 Github 小记进行连接。完成后,创建一个新的仓库,名为 <username>.github.io,<username> 为自己的用户名。打开 Settings -> Page,确保 Branch 已选择。\n运行以下命令:\n$ npm install hexo-deployer-git --save\n\n随后打开 _config.yml 文件,找到 deploy 字样,更改如下:\n# Deployment\n## Docs: https://hexo.io/docs/one-command-deployment\ndeploy:\n type: git\n repository: [email protected]:<username>/<username>.github.io.git # <username> 是你的 github 用户名\n branch: main # 具体看自己储存库的分支\n\n更改完毕后,就可以运行 hexo d 部署到 Github Pages 了。访问 https://<username>.github.io,如果第一次部署,未显示就多等一会儿。完成后,就可以看到网站了。\n修改和美化下载主题 NexTNexT 是一个很简洁美观且不断维护的 Hexo 主题。这个博客就是 Next 主题。首先,进行下载,运行以下命令:\n$ cd themes\n$ git clone https://github.com/next-theme/hexo-theme-next.git\n\n完成后,打开 config.yml,找到 theme 字样,更改如下:\n# Extensions\n## Plugins: https://hexo.io/plugins/\n## Themes: https://hexo.io/themes/\ntheme: hexo-theme-next\n\n找到 language 字样,更改其值为 zh-CN。\n此时本地运行大概是这个样子:\n可以根据需要更改 NexT 主题。打开 themes/hexo-theme-next/_config.yml(主题配置文件)找到 scheme 字样,选择一个去掉 # 注释,把原本的用 # 注释上。例如:\n# Schemes\n#scheme: Muse\n#scheme: Mist\nscheme: Pisces\n#scheme: Gemini\n\n主题效果(图片和演示链接):\nMuseMistPiscesGemini文档演示:https://theme-next.js.org/muse/文档演示:https://theme-next.js.org/mist/文档演示:https://theme-next.js.org/pisces/文档演示:https://theme-next.js.org/自己用了这个 NexT 主题,敲好看的。\n\n基础修改主页面原本的文章链接为 YYYY/MM/DD/:title(例如 /2023/11/04/hello),一大串日期很难看。我当时把他改为了 posts/:title,这样有很高的自由度(标题自定义)的同时保证了链接简短。打开整体设置 _config.yml,找到 permalink,更改为:\n # URL\n ## Set your site url here. For example, if you use GitHub Page, set url as 'https://username.github.io/project'\n url: http://example.com\n- permalink: :year/:month/:day/:title/\n+ permalink: posts/:title/\n\n当文章多的时候,一整页默认有十篇文章,很长,可以减小。打开整体设置 _config.yml,找到 per_page,将它的的值更改为 5。这样每一页就会简约得多。\n自定义浏览器里标签的图标打开主题配置文件 themes/hexo-theme-next/_config.yml,找到 favicon,把自己的图标放到 theme/hexo-theme-next/source/images 内。三种尺寸:16x16、32x32、原大小、原大小 分别对应 small medium apple_touch_icon safari_pinned_tab,将其相应位置填写好。例如我这个博客的配置是:\nfavicon:\n small: /images/icon-16x16.png\n medium: /images/icon-32x32.png\n apple_touch_icon: /images/icon.png\n safari_pinned_tab: /images/icon.png\n #android_manifest: /manifest.json\n\n菜单栏,新页面此时,新博客只有两大主页面:文章,主页。这未免有些太单薄,最好有个分类标签一类的。NexT 已经帮我们做好了。打开主题配置文件 themes/hexo-theme-next/_config.yml,找到 menu,把 home tags categories archives 前面的 # 注释都去掉。大概是这个样子:\nmenu:\n home: / || fa fa-home\n #about: /about/ || fa fa-user\n tags: /tags/ || fa fa-tags\n categories: /categories/ || fa fa-th\n archives: /archives/ || fa fa-archive\n #schedule: /schedule/ || fa fa-calendar\n #sitemap: /sitemap.xml || fa fa-sitemap\n #commonweal: /404/ || fa fa-heartbeat\n\n你也可以更改 “||” 之后的图标,去 font-awesome.com 用英文搜索你想要的图标。例如 fa-rectangle-list 显示为 \n随后运行以下命令:\n$ hexo new page tags\n$ hexo new page categories\n\n完成后打开 source/tags/index.md,在两个 --- 内新加一行 type: tags;同样地,打开 source/tags/index.md,加一行 type: categories。顺便你也可以更改它们的 title 值分别为 标签 和 分类。\n完成以上所有步骤后,你我期待的像样的一个博客诞生了。此时,新拥有博客的人可以开始写作了\n更多修改CSS(Stylus) 方面在 source 文件夹下新建文件夹 _data,在 source/_data 下新建文件 styles.styl,根据下面的代码注释插入你需要的内容:\n// 隐藏顶部线条\n.headband {\n display: none;\n}\n// 顶部边距\n.header,\n.main-inner {\n margin-top: 10px;\n\n +mobile() {\n margin-top: 0;\n }\n}\n\n// 图片圆角\n.post-body img {\n border-radius: 8px \n}\n\n// 侧边栏圆角\nheader.header {\n background: var(--content-bg-color);\n border-radius: 5px 5px 5px 5px;\n box-shadow: 0 2px 2px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12);\n}\n.site-brand-container {\n border-radius: 5px 5px 0px 0px;\n}\n.sidebar-inner {\n background: var(--content-bg-color);\n border-radius: 5px;\n box-shadow: 0 2px 2px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12), 0 -1px 0.5px 0 rgba(0,0,0,0.09);\n box-sizing: border-box;\n color: var(--text-color);\n margin-top: 12px;\n max-height: calc(100vh - 24px);\n}\n// 文章圆角\n.main-inner .sub-menu, .main-inner .post-block, .main-inner .tabs-comment, .main-inner > .comments, .main-inner .comment-position .comments, .main-inner .pagination {\n background: var(--content-bg-color);\n border-radius: 5px 5px 5px 5px;\n box-shadow: 0 2px 2px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12);\n}\n\n// 添加背景图片\nbody {\n background: url(/pic/background3.png);\n background-size: cover;\n background-repeat: no-repeat;\n background-attachment: fixed;\n background-position: 50% 50%;\n @media (prefers-color-scheme: dark) {\n background-image: none;\n }\n}\n\n随后,打开主题配置文件 themes/hexo-theme-next/_config.yml,找到 custom_file_path,把 style 子项的注释去掉。更改成这个样子:\ncustom_file_path:\n #head: source/_data/head.njk\n #header: source/_data/header.njk\n #sidebar: source/_data/sidebar.njk\n #postMeta: source/_data/post-meta.njk\n #postBodyStart: source/_data/post-body-start.njk\n #postBodyEnd: source/_data/post-body-end.njk\n #footer: source/_data/footer.njk\n #bodyEnd: source/_data/body-end.njk\n #variable: source/_data/variables.styl\n #mixin: source/_data/mixins.styl\n style: source/_data/styles.styl\n\n以后可以根据自己的需要慢慢更改 styles.styl 的内容。\n注意备份\n任何修改都有可能出现错误,建议多进行备份原生成文件。有必要的话使用 git 进行版本控制,备份到远程仓库。否则出现错误很难改回来。尤其是更改了很多的时候。\n\n\n插件:搜索功能 hexo-generator-searchdb运行以下命令下载:\n$ npm install hexo-generator-searchdb --save\n\n打开主题配置文件 themes/hexo-theme-next/_config.yml,找到 local_search 项,更改如下:\n local_search:\n- enable: false\n+ enable: true\n # If auto, trigger search by changing input.\n # If manual, trigger search by pressing enter key or search button.\n trigger: auto\n # Show top n results per article, show all results by setting to -1\n top_n_per_article: 1\n # Unescape html strings to the readable one.\n unescape: false\n # Preload the search data when the page loads.\n preload: false\n\n此时,在菜单栏中的搜索已出现,可以点击并键入进行搜索了。\n插件:置顶功能 hexo-generator-index-pin-top运行以下命令下载:\n$ npm install hexo-generator-index-pin-top --save\n\n如果需要置顶一篇文章,在这篇文章的 Front-matter 中添加:\ntop: true\n\n可以给置顶的文章增加标识。在 source/data 下新建文件 post-meta.njk:\n{% if post.top %}\n <div style=\"padding-left: 8px;\">\n <span class=\"post-meta-divider\" style=\"padding-right: 8px;\">|</span>\n <span color=black style=\"color: #000000; font-weight: bold; padding-left: 4px;\"> <i class=\"fa fa-thumbtack\"></i> </span>\n <span style=\"color: #000000; font-weight: bold; padding-left: 4px;\"> 置顶 </span>\n </div>\n{% endif %}\n\n打开主题配置文件 themes/hexo-theme-next/_config.yml,找到 custom_file_path,把 postMeta 的注释去掉。\n写作运行 hexo new name 就可以在 source/_posts 下新建一篇名为 name.md 的文章。原本是这样的:\n---\ntitle: \ndate: \ntags:\n---\n\n两个 --- 之内的内容叫做 Front-matter,它是这个文章的信息(变量)。以下是会增加和修改的东西:\n\ntitle: 文章标题\ndate: 建立日期\ntags: 标签,可以有多个。\ncategories: 分类,可以有多个。\n\n多个标签的标注方法用 - 多行,多个同级分类用 - [],如:\n---\ntags:\n- 生活\n- 美好\ncategories:\n- [生活]\n- [美好]\n---\n\n这样,这篇文章就有两个标签,两个分类。\n进行正常写作时使用 Markdown 语法,或者可以安装其他插件使用其他语言写作。\n警告\n写了好长时间,还相当于自己又建了一个博客,不建议像我这样写这么长的文章(狗头)开玩笑的啦~工作区终于清静了。。。\n\n","categories":["Programming"],"tags":["HTML"]},{"title":"哈夫曼树和哈夫曼编码","url":"//posts/huff-tree/","content":"更前面的知识:树的概念先来说说前面的芝士:\n\n路径长度 从根结点到目标结点经过的结点数量(边的数量)。\n权值 一个结点的权值可以是人为赋予的一个数。\n结点的带权路径长度 从根节点到当前结点的路径长度乘结点的权值。\n树的带权路径长度 整个树中叶子结点的带权路径长度总和。\n\n哈夫曼树是二叉树,且哈夫曼树的带权路径长度最小,哈夫曼编码会用到。\n\n\n哈夫曼树的构建前面写了,哈夫曼树的带权路径长度最小,若想带权路径最小,则权值小的结点的路径长,权值大的结点路径短。哈夫曼树构建的结点都必须是叶子结点,例如用 1 2 5 6 构建的哈夫曼树是这样的:这个树的带权路径长度为 25。\n构造过程:\n\n选出权值两个最小的结点合并;\n将两个点从将要合并的结点序列中删除,加入两个结点的和;\n重复以上步骤,直至达到要求。\n\n演示:\n哈夫曼编码基于哈夫曼树,按照字符出现的频率(也就是哈夫曼树中的权值)进行二进制编码。也就是用哈夫曼树对一串字符进行编码,可以认为左子树是 0,右子树是 1。(说不清楚啊)哈夫曼编码是贪心的思想,为了使信息量最小化,可以用到哈夫曼树。\n","categories":["CourseNotes"],"tags":["基础算法","树"]},{"title":"最长上升/公共子序列","url":"//posts/lcs/","content":"最长上升子序列即从原序列中按顺序取出数字排列在一起,保证这些数字是递增(不包括相等)的。\n\n\n第一种尝试将任意元素接到某个子序列之后。\n#include <cstdio>\n#include <algorithm>\nusing namespace std;\n\nint n;\nint dp[5003], a[5003]; // dp[i] 表示 a 中前 i 个数字的最长上升子序列\nint main()\n{\n scanf(\"%d\", &n);\n for(int i = 1; i <= n; i++)\n {\n scanf(\"%d\", &a[i]);\n dp[i] = 1;\n }\n \n for(int i = 1; i <= n; i++)\n {\n for(int j = 1; j < i; j++)\n {\n if(a[i] > a[j])\n {\n dp[i] = max(dp[i], dp[j] + 1);\n }\n }\n }\n \n int maxn = 0;\n for(int i = 1; i <= n; i++)\n {\n maxn = max(maxn, dp[i]);\n }\n printf(\"%d\\n\", maxn);\n \n return 0;\n}\n\n第二种维护一个数组储存当前的子序列。加入数字时,如果当前数字比序列末尾数字大,直接追加到末尾;否则寻找序列中第一个大于等于它的数进行替换。\n#include <cstdio>\n#include <algorithm>\nusing namespace std;\n\nint n, a[5003], dp[5003], cnt = 0;\nint main()\n{\n scanf(\"%d\", &n);\n for(int i = 0; i < n; i++)\n {\n scanf(\"%d\", &a[i]);\n \n if(i == 0) dp[cnt++] = a[i];\n else if(a[i] > dp[cnt - 1]) dp[cnt++] = a[i]; // 不可以等于!!! \n else *lower_bound(dp, dp + cnt, a[i]) = a[i];\n // lower_bound(*__first, *__last, &__val) 从 __first 到 __last 二分查找返回第一个大于等于 __val\n }\n \n // for(int i = 0; i < cnt; i++) printf(\"%d \", dp[i]);\n printf(\"%d\\n\", cnt);\n \n return 0;\n}\n\n最长公共子序列#include <cstdio>\n#include <cstring>\nusing namespace std;\n\nint dp[3003][3003]; // dp[i][j] 代表第一个字符串前 i 个字符 和 第二个前 j 个字符的最长公共子序列\nint longest_common_subsequence(char a[3003], char b[3003])\n{\n int lena = strlen(a), lenb = strlen(b);\n for(int i = 0; i <= lena; i++)\n {\n for(int j = 0; j <= lenb; j++)\n {\n if(i == 0 || j == 0) dp[i][j] = 0; // 前零个字符,没有公共子序列\n else if(a[i - 1] == b[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;\n else dp[i][j] = (dp[i - 1][j] > dp[i][j - 1]) ? (dp[i - 1][j]) : (dp[i][j - 1]);\n }\n }\n return dp[lena][lenb];\n}\n\nchar str1[3003], str2[3003];\nint main()\n{\n scanf(\"%s\", &str1);\n scanf(\"%s\", &str2);\n \n printf(\"%d\\n\", longest_common_subsequence(str1, str2));\n \n return 0;\n}\n","categories":["CourseNotes"],"tags":["动态规划"]},{"title":"有关进制的一些小记","url":"//posts/jinzhi/","content":"x 进制,代表着在这个计数方法中逢 x 进一,例如十进制就代表着逢十进一。我们平常在生活中用的都是十进制。进制一类的东西在 OI 中也比较重要CCF 喜欢考,计算机中的数据都是以二进制储存的,二进制也完美地利用了每一个比特。当然,只要有足够的表示方法,人们可以弄出三十二进制、六十四进制,甚至一千进制。闲的没事情干,进制有关的以及进制之间的转换就是我想记下来的话题。\n\n\n进制转换二进制和十进制的转换这是 €€£ CCF 出的题中一定会多少考到一点的知识,比较重要。一般来说,x 进制的数记作 $(\\text {number})_x$,例如二进制数 1011 记作 $(1011)_2$,十进制数 114514 记作 $(114514)_{10}$。\n二进制 -> 十进制二进制的数从右往左每一位都有权值,第 i 位的权值为 $2^{(i - 1)}$。举个例子:二进制数 1011001 的每一位权值是:$\\mathbf{1} \\to 2^6 \\ \\ \\ \\mathbf{0} \\to 2^5 \\ \\ \\ \\mathbf{1} \\to 2^4 \\ \\ \\ \\mathbf{1} \\to 2^3 \\ \\ \\ \\mathbf{0} \\to 2^2 \\ \\ \\ \\mathbf{0} \\to 2^1 \\ \\ \\ \\mathbf{1} \\to 2^0$。\n从二进制转换为十进制只需要将当前二进制位的值乘上权值即可。还是 1011001:\n\\begin{aligned}\n&(1011001)_2 \\\\\n= \\ &(\\mathbf{1} \\times 2^6) + (\\mathbf{0} \\times 2^5) + (\\mathbf{1} \\times 2^4) + (\\mathbf{1} \\times 2^3) + (\\mathbf{0} \\times 2^2) + (\\mathbf{0} \\times 2^1) + (\\mathbf{1} \\times 2^0) \\\\\n= \\ &64 + 0 + 16 + 8 + 0 + 0 + 1 \\\\\n= \\ &(89)_{10}\n\\end{aligned}\n\n可见,二进制 1011001 转换为十进制是 89。话说 hexo next 的 mathjax 渲染越来越奇怪了。。。\n十进制 -> 二进制可以使用短除法,将十进制除以二取余,直至商为零。最后将取余的结果倒序输出。比如说,还是那个数字 $(89)_{10}$:\n\\begin{aligned}\n2 \\ | \\underline{ \\ \\ \\ \\ \\ \\ \\ \\ } &\\underline{89 \\ } \\\\\n2 \\ | \\underline{ \\ \\ \\ \\ \\ \\ \\ } &\\underline{44 \\ } \\ \\ \\ \\ \\ \\ \\textbf{1} \\\\\n2 \\ | \\underline{ \\ \\ \\ \\ \\ \\ } &\\underline{22 \\ } \\ \\ \\ \\ \\ \\ \\textbf{0} \\\\\n2 \\ | \\underline{ \\ \\ \\ \\ \\ } &\\underline{11 \\ } \\ \\ \\ \\ \\ \\ \\textbf{0} \\\\\n2 \\ | \\underline{ \\ \\ \\ \\ } &\\underline{\\ \\ 5 \\ } \\ \\ \\ \\ \\ \\ \\textbf{1} \\\\\n2 \\ | \\underline{ \\ \\ \\ } &\\underline{\\ \\ 2 \\ } \\ \\ \\ \\ \\ \\ \\textbf{1} \\\\\n2 \\ | \\underline{ \\ \\ } &\\underline{\\ \\ 1 \\ } \\ \\ \\ \\ \\ \\ \\textbf{0} \\\\\n2 \\ | &\\underline{\\ \\ 0 \\ } \\ \\ \\ \\ \\ \\ \\textbf{1}\n\\end{aligned}\n\n最后,倒着输出即可得知 $(89)_{10} = (1011001)_2$ 。ps: mathjax 没有特定的短除公式,只好用这种奇特的方法模拟短除。\nP.S. 另一种更方便的双向转换方法\n可以发现,其实二进制十进制的互相转换都离不开 二的 i 次方,也可以将这张表记下来(也就是从右往左不断地乘二嘛),之后的转换会方便很多。其实这张表也就是二的 i 次方:\n... 256 \\ \\ 128 \\ \\ 64 \\ \\ 32 \\ \\ 16 \\ \\ 8 \\ \\ 4 \\ \\ 2 \\ \\ 1\n\n二进制进制转十进制十进制转二进制这算是这张表最方便的用法了,还是 $(1011001)_2$\n\n\n\n每一位\n1\n0\n1\n1\n0\n0\n1\n\n\n\n每一位对应的权值\n64\n32\n16\n8\n4\n2\n1\n\n\n一代表着要加起来,否则不加起来,那么,这个二进制数等于这个十进制数:$(1011001)_2 = 64 + 16 + 8 + 1 = (89)_{10}$同样的,也可以一位一位地尝试累加。假如十进制减去奶味的权值不小于 0,就减去,否则不减去,直至加起来的权值之和等于目标十进制数。例如十进制数 89: \n\n\n\n权值表\n64\n32\n16\n8\n4\n2\n1\n\n\n\n是否减去\n1\n0\n1\n1\n0\n0\n1\n\n\n过程备注\n初始的值是 89,64 < 89,就减去,89 - 64 = 25。结果 1。\n32 > 25,不减去。结果 0。\n16 < 25,25 - 16 = 9。结果 1。\n8 < 9,9 - 8 = 1。结果 1。\n4 > 1。结果 0。\n2 > 1。结果 0。\n1 = 1,1 - 1 = 0。结果 1。\n\n\n可见:$(89)_{10} = (1011001)_2$\n\n\n其他进制之间的转换像其他进制,比如十六进制,大于 9 时就可以用字母 A B C D E F 按照顺序代替数字。\n使用十进制当作媒介的转换(对于所有进制通用)其实,任何进制之间的转换都可以将那个进转换为十进制后再转换为目标进制。十进制也可以转换为任何进制。至于怎么转换,其实和 二进制和十进制之间的转换 差不多。同样的,x 进制转十进制 每一位的 每一位乘上每一位的权值 之和。例如:\n\\begin{aligned}\n&(1021102)_3 \\\\\n= \\ &(\\mathbf{1} \\times 3^6) + (\\mathbf{0} \\times 3^5) + (\\mathbf{2} \\times 3^4) + (\\mathbf{1} \\times 3^3) + (\\mathbf{1} \\times 3^2) + (\\mathbf{0} \\times 3^1) + (\\mathbf{2} \\times 3^0) \\\\\n= \\ &729 + 0 + 162 + 27 + 9 + 0 + 2 \\\\\n= \\ &929\n\\end{aligned}\n\n$x$ 进制的从右往左数(从 1 开始数)第 $i$ 位的权值就是 $x^{(i - 1)}$。转换为十进制只需要将每一位的 每一位的权值乘那一位的数 加起来即可。\n十进制转 x 进制也可以用短除法,不断整除 x,取余,然后倒序输出。照理说,将十进制作为媒介可以将任意进制转换为其他任意进制。同样的,也可以通过代码更方便地实现(说一句,还是别人的代码写的好看,我写的屎山简直不忍直视,而且只能大到十六进制):\n#include <iostream>\n#include <cstdio>\n#include <cstring>\n#include <cmath>\nusing namespace std;\n\nint xtoten(int x, string s) // x 进制转十进制\n{\n int tensum = 0, cnt = 0 ;\n for(int i = s.length() - 1; i >= 0; i--) // 从右往左求权值\n {\n int t;\n if(s[i] == '0') t = 0; // 屎山代码的本质。。。\n else if(s[i] == '1') t = 1;\n else if(s[i] == '2') t = 2;\n else if(s[i] == '3') t = 3;\n else if(s[i] == '4') t = 4;\n else if(s[i] == '5') t = 5;\n else if(s[i] == '6') t = 6;\n else if(s[i] == '7') t = 7;\n else if(s[i] == '8') t = 8;\n else if(s[i] == '9') t = 9;\n else if(s[i] == 'A') t = 10;\n else if(s[i] == 'B') t = 11;\n else if(s[i] == 'C') t = 12;\n else if(s[i] == 'D') t = 13;\n else if(s[i] == 'E') t = 14;\n else if(s[i] == 'F') t = 15;\n tensum += t * pow(x, cnt); // $x$ 进制的从右往左数(从 1 开始数)第 $i$ 位的权值就是 $x^{(i - 1)}$。\n cnt++ ;\n }\n return tensum;\n}\nstring tentoy(int y, int n) // 十进制转 x 进制\n{\n string ret = \"\" ;\n for( ; ; )\n {\n int t = n % y; // 除以 x 剩下的余数\n if(t == 0) ret += '0'; // 屎山依旧。。。\n else if(t == 1) ret += '1';\n else if(t == 2) ret += '2';\n else if(t == 3) ret += '3';\n else if(t == 4) ret += '4';\n else if(t == 5) ret += '5';\n else if(t == 6) ret += '6';\n else if(t == 7) ret += '7';\n else if(t == 8) ret += '8';\n else if(t == 9) ret += '9';\n else if(t == 10) ret += 'A';\n else if(t == 11) ret += 'B';\n else if(t == 12) ret += 'C';\n else if(t == 13) ret += 'D';\n else if(t == 14) ret += 'E';\n else if(t == 15) ret += 'F';\n n /= y; // 整除\n if(n <= 0)\n {\n break ; // 除到零为止\n }\n }\n return ret;\n}\n\nint main()\n{\n int n, m;\n string qwq;\n \n cin >> n >> qwq >> m; // n: x 进制; qwq: 一个 x 进制的数; m: 需要转换成的进制\n \n string ans = tentoy(m, xtoten(n, qwq));\n for(int i = ans.length() - 1; i >= 0; i--) // 十进制转 x 进制需要倒序输出\n {\n cout << ans[i];\n }\n \n return 0 ;\n}\n// 比如输入 3 1021102 10 会输出 929。\n// 其实这个代码就是 洛谷 P1143 的代码 https://www.luogu.com.cn/problem/P1143\n\n将二进制转换为八进制、十六进制注意一下,二进制并不可以直接转换为其他进制,只不过对于二进制转八进制、十六进制比较方便。若是这二进制转八进制或十六进制,要是嫌使用十进制作为媒介比较麻烦,那就可以用二进制作为媒介更加方便一些。 \n将二进制转换为八进制,可以从右往左三位三位分开来,再将那三位二进制转换为十进制,合起来(注意是字符意义上的合起来)就是八进制。十六进制则是四位四位分开来。这里举两个例子:\n二进制转八进制二进制转十六进制\\because\n\\underset{\\text{八进制:}}{\\text{二进制:}} ( \\underset{1}{\\underline{1}} \\ \\underset{2}{\\underline{010}} \\ \\underset{7}{\\underline{111}} )_{2}\n\\\\\n\\therefore\n(1010111)_{2} = (127)_{8}\\because\n\\underset{\\text{十六进制:}}{\\text{二进制:}} ( \\underset{3}{\\underline{11}} \\ \\underset{\\text{A}}{\\underline{1010}} \\ \\underset{1}{\\underline{0001}} )_{2}\n\\\\\n\\therefore\n(1110100001)_{2} = (\\text{3A1})_{16}\n\nP.S. 二进制、八进制、十进制、十六进制在 C++ 中的表示方法(前缀)\n以上这些进制自然有自己的表示方法。其中,二进制以 0b 开头;八进制以 0 开头;十进制就是平常的写法,没有任何前缀;十六进制以 0x 开头。例如,以下代码会输出四个 2147483647。\nprintf(\"%d %d %d %d\\n\", \n 0b1111111111111111111111111111111, // 二进制\n 017777777777, // 八进制\n 2147483647, // 十进制\n 0x7fffffff); // 十六进制\n\n进制小数之间的转换进制小数也是 CCF 要考的一点,恰好在某个模拟赛上做到了,更新一下下。\n十进制小数转 x 进制简单来说就是小数点前面正常转换,小数点之后乘 x 取整,正常输出。例如十进制 0.3 转换为二进制:\n\\begin{aligned}\n&0.3 \\times 2 = \\mathbf{0}.6 \\\\\n\\text{二进制小数:}&0.\\mathbf{0} \\\\\n\\\\\n&0.6 \\times 2 = \\mathbf{1}.2 \\\\\n\\text{二进制小数:}&0.0\\mathbf{1} \\\\\n\\\\\n&0.2 \\times 2 = \\mathbf{0}.4 \\\\\n\\text{二进制小数:}&0.01\\mathbf{0} \\\\\n\\\\\n&0.4 \\times 2 = \\mathbf{0}.8 \\\\\n\\text{二进制小数:}&0.010\\mathbf{0} \\\\\n\\\\\n&0.8 \\times 2 = \\mathbf{1}.6 \\\\\n\\text{二进制小数:}&0.0100\\mathbf{1} \\\\\n\\\\\n&0.6 \\times 2 = \\mathbf{1}.2 \\\\\n\\text{二进制小数:}&0.01001\\mathbf{1} \\\\\n\\\\\n&...\n\\end{aligned}\n\n最后可以得出:\n(0.3)_{10} = (0.0\\dot{1}00\\dot{1})_2\n\n注:无限循环小数\nx 进制小数转十进制其实和整数位很像,不过从左往右第 i 位的权值是 -i。不写了吧。。。\n位运算位运算是一个比较毒瘤有趣的运算,是二进制的运算。当然也可以通过位运算做一些与平常的(逻辑)运算符等价的运算,但速度更快。\n按位与运算 &将两个二进制的每一位逐个比较,若这一位都为 1 则得出 1,否则得出 0。若这两个二进制数字位数不同可以在前面补零。\n1 & 1 = 1;\n0 & 0 = 0;\n1 & 0 = 0;\n0 & 1 = 0;\n\n这个运算符还有一个备选关键字:bitand,比如 10 & 3 等价于 10 bitand 3。其实所有位运算也可以在 C++ 中用十进制直接运算,例如 10 & 3,用 0b 前缀也行,也就是 0b1010 & 0b11 或 0b1010 & 0b0011。举个更详细的例子:\n\\begin{matrix}\n& 1011001 \\\\\n\\& & 0111101 \\\\\n\\hline\n& 0011001\n\\end{matrix}\n\n转为十进制就是 89 & 61 = 15。\n按位或运算 |两个二进制的每一位比较,若有一个为 1 则得出 1,否则得出 0。同样的,若这两个二进制数字位数不同可以在前面补零。\n1 | 1 = 1;\n0 | 0 = 0;\n1 | 0 = 1;\n0 | 1 = 1;\n\n这个运算符也有一个备选关键字 bitor,10 | 3 等价于 10 bitor 3。例子:\n\\begin{matrix}\n& 1011001 \\\\\n| & 0111101 \\\\\n\\hline\n& 1111101\n\\end{matrix}\n\n转为十进制就是 89 | 61 = 125。\n按位非运算 ~这算是最简单的运算符了,即将每一位取反。例如 ~0 就等于 1,~1 就等于 0。例子:\n\\begin{matrix}\n\\sim & 0111101 \\\\\n\\hline\n& 1000010\n\\end{matrix}\n\n按位异或 ^其实就是比较每一位是否相同,若相同为 0, 不相同为 1。\n1 ^ 1 = 0; \n0 ^ 0 = 0; \n1 ^ 0 = 1; \n0 ^ 1 = 1;\n\nC++ 中也有备选关键字,就是 xor。10 ^ 3 等价于 10 xor 3。例如:\n\\begin{matrix}\n& 1011001 \\\\\n\\text{\\textasciicircum} & 0111101 \\\\\n\\hline\n& 1100100\n\\end{matrix}\n\n转为十进制就是 89 ^ 61 = 100。\n左移 << 右移 >>将所有二进制位全部左移,也就是将最左边的二进制位丢弃,右边补上一个 0。例如:10110011 << 1 = 01100110。右移也是一样,不过负数往左边补 1,正数补 0。\nP.S. 位运算时赋值\n同 += -= 等符号一样,位运算也可以在符号后面加上 =,>>= ^= &= <<= |= 等运算符都是可以的。\n\n\nP.S. 位运算的一些使用技巧\n位运算其实有很多奇怪的应用。例如:\n\n判断偶数奇数(能否被 2 整除)。0 是偶数 1 是奇数。([number] & 1) == 1 相当于 ([number] % 2) == 1,平常还是写 [number] & 1。举个例子,10 & 1 = 0、13 & 1 = 1。\n求 2 的几次方,1 << [number] 就是求 2 的 [number] 次方。例如 1 << 10 = sqrt(2, 10) = 1024。\n交换 a b 两个数字。可以 a ^= b; b ^= a; a ^= b,效率比普通交换要高。\n正数变负数,负数变正数。假设一个数字 n,只需要 ~n + 1 就可以转变该数正负号。例如 ~1024 + 1 = -1024、~-114 + 1 = 114。(在 “补码” 中,详见下面的二进制编码)\n除以 2,使用 [number] >> 1。例如 100 >> 1 等价于 100 / 2,再比如 int a = 1024; a >>= 1; printf("%d\\n", a); 输出 512。\n\n其他的应用,这里不写了,有兴趣可以去网上找更多的。\n\n\n二进制的编码其实,刚才讲的(个别除外)二进制都是二进制中编码的一种:原码。二进制一共有三个编码:原码、反码和补码,计算机中真正使用的是补码。这些编码都要规定它们的位数,否则就弄不清楚到底是正数还是负数了。在下面的随记中,我用的是8 位整型。其实在 C++ 中,int 是 32 位整型。确定位数很重要,例如:\nint a = 0b11111111111111111111111111111111;\nlong long b = 0b11111111111111111111111111111111; // int a 格式化了一下,更方便看\nprintf(\"%d %lld\\n\", a, b);\n\n会输出 -1 4294967295。因为 int 是 32 位整型,而 long long 是 64 位。补码的第一位是符号位,若为 1 就是负数。而我给的二进制是 32 位,第一位是 1,int 就是负数。\n原码、反码、补码的表示是将二进制用中括号括起来,再右下角写上 “原” “反”或“补”。例如 $[00001010]_{\\text{原}}$、$[11101110]_{\\text{补}}$。\n原码原码、反码以及补码的最左边的那一位都是符号位。例如 $[00000001]_{\\text{原}}$ 是十进制的 1,而 $[10000001]_{\\text{原}}$ 是十进制的 -1。\n在原码中,除符号位外,剩下的二进制都是按照 二进制转十进制 一样。若符号位是 1 那就将转换的十进制加个负号。例如:\n\\begin{aligned}\n(5)_{10} &= [00000101]_{\\text{原}} \\\\\n(-12)_{10} &= [10001100]_{\\text{原}}\n\\end{aligned}\n\n反码原码变成反码,若原码是正数(符号位为 0)则不需要做任何改变;若原码是负数(符号位为 1)则将除符号位以外的位全部取反。例如:\n\\begin{aligned}\n(24)_{10} &= [00011000]_{\\text{原}} = [00011000]_{\\text{反}} \\\\\n(-17)_{10} &= [10010001]_{\\text{原}} = [11101110]_{\\text{反}}\n\\end{aligned}\n\n补码其实这才是计算机真正使用的二进制编码,前面的两种编码基本上只供学习和理解用。\n若那个二进制为正数,原码、反码和补码相同;若为负数,那么它的补码是它的反码加一。(请注意,二进制加法逢二进一)例如:\n\\begin{aligned}\n(27)_{10} &= [00011011]_{\\text{原}} = [00011011]_{\\text{反}} = [00011011]_{\\text{补}} \\\\\n(-53)_{10} &= [10110101]_{\\text{原}} = [11001010]_{\\text{反}} = [11001011]_{\\text{补}}\n\\end{aligned}\n\nP.S. 补码的快速转十进制方法\n同普通二进制转十进制一样,可以弄一张差不多一样的表,不同的是,这张表的最左边的数(符号位)是负数。还是以 8 位整型为例,这张表是这样的:\n-128 \\ \\ 64 \\ \\ 32 \\ \\ 16 \\ \\ 8 \\ \\ 4 \\ \\ 2 \\ \\ 1\n\n刚才的 -53 就可以以这种方法转换:\n\\begin{aligned}\n&[11001011]_{\\text{补}} \\\\\n= &-128 + 64 + 0 + 0 + 8 + 0 + 2 + 1 \\\\\n= &(-53)_{10}\n\\end{aligned}\n\n","categories":["Programming"],"tags":["语言入门"]},{"title":"线性数据结构:链表的模板","url":"//posts/lianbiao/","content":"链表类似于数组,与数组不同的是,链表可以更加方便地更改数据和删除数据。数组若想将中间的数据删除,则要非很大功夫,而链表就不同了,它的操作更加简单一些(后面说)。\n链表的数据组可以叫做“结点”,结点分成两个部分:一个是数据域,一个是指针域,数据域存数据,指针域指向下一个结点的数据地址。正是指针域将链表的每一个结点连在了一起。这种特性有一个好处:内存地址可以不连续,而数组的内存地址是必须要连续的。比如内存还有 2GB 空闲,我申请了一个 1GB 大的数组,理论上是可以申请下来的,但占用的内存不一定完全是连续的。假设内存被一大堆东西占用的零零碎碎:确实有 2GB,但分成 4 个 500MB,这就申请不下来。而链表呢,可以充分利用内存碎片,通过指针变量,将分开的数据连在一起。\n\n\n普通的链表链表还有一个好处:它是动态的,也就是说,使用的内存想申请就申请,想销毁就销毁(C/C++中,其他语言我不确定),可以节约内存。申请内存,可以用到 <malloc.h> 头文件中的 malloc() 函数,只有一个参数,填上你想要申请的内存大小(字节),可以和 sizeof 一起用。但它返回的是 void 类型,所以最好在它前面加上一个类型强制转换。而销毁内存,则可以用到这个头文件中 free() 函数,一个参数,往里面填上地址(指针变量)即可销毁,但从此不可以再调用 使用这个内存的变量,若调用会报错,需要注意。\n无论什么链表,还要有一个头指针,以便寻找元素时更好的去找。链表的结点一般用一个结构体,结构体里面一个是数据(data),一个是存着下一个结点数据地址的指针变量(next)。示例代码如下:\nstruct node\n{\n int x;\n node *next;\n};\n\nnode *head;\n\n首先说直接往末尾加上元素。先要判断链表是否为空,可以通过头指针 head 是否为空(NULL),若是第一个便创建新结点,申请为 node 类型的大小的内存,将那个结点的数据域赋值为加上的数据,再将结点的指针域设为 NULL(以防万一),将 head 设为新结点的地址。否则通过指针域穷举当前指针域是否为 NULL,也就是最后一个元素,若到了最后一个元素,则申请内存,新建结点,数据域赋值,将上一个结点的指针域赋值为当前结点数据域的地址,将打钱结点指针域设为 NULL。示例代码如下:\nvoid push(int data)\n{\n if(head == NULL)\n {\n node *New = (node *) malloc(sizeof(node));\n (*New).x = data;\n (*New).next = NULL;\n head = New;\n }\n else\n {\n node *s = head;\n while((*s).next != NULL)\n {\n s = (*s).next;\n }\n node *New = (node *) malloc(sizeof(node));\n (*New).x = data;\n (*New).next = NULL;\n (*s).next = New;\n }\n}\n\n插入也差不多,穷举到目标位置,申请内存,更改指针域。访问即是穷举,顺着指针走。更改数据还要穷举,将数据域改掉就好了。重点将删除。首先,判断删除的是否是第一个,若是则将 head 更改为下一个结点的指针域。否则穷举目标,新建一个 node 类型的零时变量,将它赋值为删除目标的下一个结点的指针域,销毁准备删除的内存,将删除的地方的指针域赋值为那个零时变量。完整代码:\n#include <malloc.h>\n#include <cstdio>\nusing namespace std;\n\nstruct node // 结点\n{\n int x; // 数据\n node *next; // 下一个结点的地址\n};\n\nnode *head; // 指针变量\n\nvoid push(int data) // 往末尾追加元素,`data` 是要追加的数据\n{\n if(head == NULL) // 链表为空\n {\n node *New = (node *) malloc(sizeof(node)); // 申请内存\n (*New).x = data; // 存数据\n (*New).next = NULL; // 以防万一\n head = New; // 因为链表是空的,所以要给头指针赋值。\n }\n else\n {\n node *s = head; // 开始遍历\n while((*s).next != NULL) // 条件的意思是不为链表的最后一个\n {\n s = (*s).next; // 通过下一个结点的地址不但遍历\n }\n node *New = (node *) malloc(sizeof(node)); // 同上的 `head==NULL`\n (*New).x = data;\n (*New).next = NULL;\n (*s).next = New;\n }\n}\nvoid insert(int x, int y) // 插入, `x` 是要加的数据,`y` 表示在链表的第 `y` 个元素后插入数据\n{\n node *s = head;\n y-- ;\n while(y)\n {\n s = (*s).next;\n y-- ;\n }\n node *New = (node *) malloc(sizeof(node));\n (*New).x = x;\n (*New).next = (*s).next;\n (*s).next = New;\n}\nint find(int x) // 返回链表的第 `x` 个结点的数据\n{\n node *s = head;\n x-- ;\n while(x)\n {\n s = (*s).next;\n x-- ;\n }\n return (*s).x;\n}\nvoid update(int x, int y) // 更改链表第 `x` 个结点的数据域为 `y`\n{\n node *s = head;\n x-- ;\n while(x)\n {\n s = (*s).next;\n x-- ;\n }\n (*s).x = y;\n}\nvoid deletes(int x) // 删除链表第 `x` 个结点\n{\n if(x == 1)\n {\n head = (*head).next;\n return;\n }\n node *s = head;\n x-- ;\n x-- ;\n while(x--)\n {\n s = (*s).next;\n x-- ;\n }\n node *t = (*((*s).next)).next; // 零时指针变量,下下个结点的指针域\n free((*s).next); // 销毁内存\n (*s).next = t;\n}\n\nint main() // main() 是示例\n{\n push(100); // 在末尾追加 100\n push(200); // 在末尾追加 200\n insert(300, 1); // 在第一个结点的后面加上 300\n printf(\"first:%d, second:%d, third:%d\\n\", find(1), find(2), find(3)); // 链表现在为 100 300 200\n deletes(1); // 删掉第一个元素\n insert(400, 1); // 在第一个结点的后面插入 400\n printf(\"first:%d, second:%d, third:%d\\n\", find(1), find(2), find(3)); // 链表现在为 300 400 200\n return 0;\n}\n\n虽然代码注释讲了,为了更清楚,再说一遍输出:\nfirst:100, second:300, third:200\nfirst:300, second:400, third:200\n附演示:https://visualgo.net/zh/list\n","categories":["CourseNotes"],"tags":["基础算法","数据结构"]},{"title":"前缀和","url":"//posts/qianzhuihe/","content":"开始前缀和是一种优化算法,用于求区间和。若数据范围特别大,写 for 循环很可能会爆时间复杂度,就可以用上前缀和了。前缀和有一维前缀和和二维前缀和,我暂时还没有学二位前缀和,故在此不多赘述。\n使用一维前缀和需要把一个数组比如数组 $a[1]$ 到 $a[n]$ ($n$ 为 $a$ 数组长度)储存到另一个数组中比如 数组 $b$。那么:\nb[i] \\ (i \\le n) = \\displaystyle\\sum_{j = 1}^{i} a[j]\n\n\n\n我们发现:\nb[1] = a[1] \\\\\nb[2] = a[1] + a[2] \\\\\nb[3] = a[1] + a[2] + a[3] \\\\\n... \\\\ \nb[i - 1] = a[1] + a[2] + ... + a[i - 1] \\\\ \nb[i] = a[1] + a[2] + ... + a[i - 1] + a[i] \\\\\n~\\\\\n\\therefore b[i] = b[i - 1] + a[i]\n\n这正好是一个递推的过程,$b[1] = a[1], \\ b[2] = b[1] + a[2] \\ …$\n同时,若 $l$ 为左边界, $r$ 为又边界,$b[r] - b[l - 1] = a[r]$ 到 $a[l]$ 的区间和。\n例题前缀和模板题目描述给出一个数字$n$表示有个数字,\n给出$n$个整数$a_1$,$a_2$,…$a_n$;\n给出一个数字$m$ 有$m$个询问:每次询问给出两个整数$s$,$e$,请求出 $a_s + a_{s+1}…a_e$\n输入格式第一行一个整数$n$\n第二行$n$个整数$a_1$,$a_2$,…$a_n$;\n第三行一个整数$m$\n随后m行每行两个整数 s,e,($e >= s$)\n输出格式m个整数,每一个换一行\n样例 #1样例输入 #15\n1 2 3 4 5\n3\n1 2\n2 3\n1 5\n\n样例输出 #13\n5\n15\n\n提示$n <= 10^5$,$a_i <= 10^4$。\n\n\n这道题目应该这样写:\n#include <cstdio>\nusing namespace std ;\n\nint a[100001], b[100001] ;\nint n, m ;\nint main()\n{\n\tscanf(\"%d\", &n) ;\n\tfor(int i = 1; i <= n; i ++)\n\t{\n\t\tscanf(\"%d\", &a[i]) ;\n\t}\n\t\n\tb[1] = a[1] ;\n\tfor(int i = 1; i <= n; i ++)\n\t{\n\t\tb[i + 1] += b[i] + a[i + 1] ;\n\t}\n\t\n\tscanf(\"%d\", &m) ;\n\tfor(int i = 1; i <= m; i ++)\n\t{\n\t\tint s, e ;\n\t\tscanf(\"%d %d\", &s, &e) ;\n\t\tprintf(\"%d\\n\", b[e] - b[s - 1]) ;\n\t}\n\t\n\treturn 0 ;\n}","categories":["CourseNotes"],"tags":["基础算法","前缀和,差分"]},{"title":"命名空间","url":"//posts/namespace/","content":"C++命名空间的概念在同一个作用域中,不同的数据不能起同一个名字,但是C++命名空间概念的出现,提供了解决问题的方案。在不同的命名空间中,可以随意定义相同的名字。命名空间就是为了避免你包含的头文件中与你自己定义的任意类,数据,函数重名,造成令人迷惑的错误而产生的。\n\n\n定义命名空间我们可以自己定义一个命名空间,并且使用它。定义命名空间使用 namespace 关键字,使用命名空间使用 namespace::subject 使用命名空间中的函数,数据等。例如:\n#include <stdio.h> // 方便演示,使用了C头文件\n\nnamespace mylib\n{\n int a = 1, b = 2, c = 3 ;\n void hello()\n {\n printf(\"Hello World!\\n\") ;\n }\n}\nnamespace libbb\n{\n int a = 10, b = 20, c = 30 ;\n void hello()\n {\n printf(\"HELLO WORLD!!!!\\n\") ;\n }\n}\n\nint main()\n{\n printf(\"%d %d %d\\n\", mylib::a, mylib::b, mylib::c) ;\n printf(\"%d %d %d\\n\", libbb::a, libbb::b, libbb::c) ;\n mylib::hello() ;\n libbb::hello() ;\n \n return 0 ;\n}\n\n输出 1 2 3\\n 10 20 30\\n Hello World!\\n HELLO WORLD!!!!\\n。在命名空间 $mylib$ 和 $libbb$ 中,三个变量和一个函数的名字相同,但是所调用的命名空间不同,结果也不一样。\n在C++中,大部分函数都在命名空间 $std$ 中,全称 $stdandard$ 。\nusing使用命名空间在上段程序中,我们可以在包含头文件后加入几句:\nusing namespace mylib ;\n\n这样 $mylib$ 命名空间里的 $a ~ b ~ c ~ hello()$ 可以直接写为它原本的样子,不用加上 mylib:: 。这很方便。但是这种方法也有他的局限性,比如我再加入一句:\nusing libbb::a ;\n\n这样 $libbb$ 命名空间里的 $a$ 使用时也不用加上 libbb:: 了。但是再次出现了两个同样的 $a$ ,谁也分不清使用的到底是 $mylib$ 命名空间里的 $a$ 还是 $libbb$ 命名空间里的 $a$ ,因此会引发错误,这也是 using 的弊端。但是有时候只会用到一个命名空间里的东西时,就比如 $std$ ,就可以直接加上一句 using namespace std ; 这样子更方便,省的 cin 也要 std:: , string 也要 std 。\n","categories":["CourseNotes"],"tags":["语言入门"]},{"title":"递归","url":"//posts/recursion/","content":"\n写递归的要点明白一个函数的作用并相信它能完成这个任务,千万不要跳进这个函数里面企图探究更多细节, 否则就会陷入无穷的细节无法自拔,人脑能压几个栈啊。—— OI-wiki\n\n递归,就是一个函数自身调用自身。递归起到类似与循环的效果。但是,与循环不同,递归可以分支。如果循环一定是一条直线,那么递归可能是树形结构。\n循环 -> 递归前面说了,循环和递归很像。那么,我们可以将 for 循环尝试转为递归。先来一个循环的示例:\nfor(int i = 1; i <= n; i++)\n{\n printf(\"qwq, %d\\n\", n);\n}\n\n首先,让我们来想一想,for 循环的括号中 3 个语句分别是干什么的呢?\n\nint i = 1; 这是循环的初始化,定义了一个变量 $i$,将其赋值为 $1$。\ni <= n; 这是循环每次进行下去的条件,当 $i>n$ 时即退出循环。\ni++ 这是循环每次结束后干的事,当执行完循环体时, $i$ 则加 $1$。\n\n这样回忆下来,可以发现,在 for 循环的括号中 3 个语句其实可以拆分出来。如下:\nint i = 1; // int i = 1;\nfor( ; ; )\n{\n if(i > n) break; // i <= n;\n printf(\"qwq, %d\\n\", n);\n i++ ; // i++\n}\n\n那么,直接将 for( ; ; ) 改一下就好了吧?就像这样子:\nint i = 1;\nvoid rcsn()\n{\n if(i > n) break;\n printf(\"qwq, %d\\n\", n);\n i++ ;\n}\n等等,只将 for( ; ; ) 改为 void rcsn() 似乎不太对,少了什么语句,函数不会自动循环(递归)啊。还有,函数哪儿来的 break;?是的,递归,就是要自己调用自己。函数的结束,是该使用 return。应该这样修改:\n int i = 1;\n void rcsn()\n {\n- if(i > n) break;\n+ if(i > n) return;\n printf(\"qwq, %d\\n\", n);\n i++ ;\n+ rcsn();\n }\n好了,这样就可以完整地运行了:\n#include <stdio.h>\n\nint n;\nint i = 1;\nvoid rcsn()\n{\n if(i > n) return;\n printf(\"qwq, %d\\n\", n);\n i++ ;\n rcsn();\n}\n\nint main()\n{\n scanf(\"%d\", &n);\n rcsn();\n}\n\n递归的分步思想前面说了,递归是可以分支的。那么,它其实比循环方便的多。就比如说,输入一个整数 $n$,按照字典序输出 $1 \\sim n$ 数字不重复的排列。$1 \\le n \\le 9$。\n总不可能用 if 一个一个判断,然后来一个“循环 $n$ 嵌套”吧。而递归是可分支的。可以创建一个递归函数,在递归中使用 for 循环确定递归次数。用一个数组记录是否重复。代码如下:\n#include <iostream>\nusing namespace std;\n\nint n;\nbool flag[12];\nint a[15];\n\nvoid dg(int id)\n{\n if(id > n)\n {\n for(int i = 1; i <= n; i++)\n {\n cout << \" \" << a[i];\n }\n cout << endl;\n return;\n }\n \n for(int i = 1; i <= n; i++)\n {\n if(flag[i]) continue;\n \n flag[i] = 1;\n a[id] = i;\n dg(id + 1);\n flag[i] = 0;\n }\n}\n\nint main()\n{\n ios::sync_with_stdio(false);\n cin >> n;\n dg(1);\n \n return 0;\n}\n\n但是,递归并不是刚完成就返回,而是完成了整个分支才返回。以 $n=3$ 为例,画个上面那个递归函数的图:(说明:圆圈中的数字是前进的顺序,从小到大;实线箭头和虚线箭头先走实线,走完以后再走虚线;箭头上的数字代表输出的值。\n递归的分治思想分治,就是将一个问题分解为多个问题,然后再进行解决。用咱们老师的一个词概括,就是:\n\n分而治之\n\n举个例子:一件工程做 100 个零件,接活的找了 10 个人帮忙,那 10 个人又去找了 10 个人,每一组的 10 个人做完了向上头汇报,上头 10 个人又向接活的人汇报。这其实就是一个递归分治的过程,这么一个例子体现了分治的基本步骤:\n\n分解:“接活的找了 10 个人帮忙,那 10 个人又去找了 10 个人” -> 将原问题分解成子问题\n解决:“每一组的 10 个人做完了” -> 子问题独立求解\n合并:“(10 个人做完了)向上头汇报,上头 10 个人又向接活的人汇报。” -> 将子问题合并为原问题\n\n当分解到指定条件时,就开始解决——通常是直接返回特定的数据。\n题目举例:CodeForces 1829D这道题目要分解为两个任务,第一是总金币数的三分之一,第二是总金币数的三分之二。分解停止开始解决的的条件有三个,分别是 $x==m$(符合条件),$x<m$(不符合条件),$x % 3 \\ne 0$(不符合条件)。可以这样想:若符合条件返回 $1$,不符合返回 $0$,将返回结果相加。若最终结果大于零,输出 YES,否则输出 NO。代码如下:\n#include <cstdio>\nusing namespace std;\n\nint n, m;\nint t;\n\nint dg(int x)\n{ \n if(x == m) return 1;\n if(x < m || x % 3 != 0) return 0;\n \n \n int ans1 = dg(x / 3);\n int ans2 = dg(x / 3 * 2);\n // printf(\"ans1:%d, ans2:%d\\n\", ans1, ans2);\n \n return ans1 + ans2;\n}\n\nint main()\n{\n scanf(\"%d\", &t);\n \n for(int i = 1; i <= t; i++)\n {\n scanf(\"%d %d\", &n, &m);\n \n int ans = dg(n);\n // printf(\"ans:%d\\n\", ans);\n if(ans > 0) printf(\"YES\\n\");\n else printf(\"NO\\n\");\n }\n \n return 0;\n}\n\n剪枝题外话:感觉和递归有关的分类都一团乱了,感觉 DFS 原本应该放在同一篇文章里的,剪枝和分治也应该独立说一篇。\n简短的概述:可以说,递归也就是暴力。暴力有两个代名词:枚举、递归。同枚举差不多,递归也有优化的方案,那就是剪枝。剪枝,顾名思义,就是把不需要的分支剪掉,把不可能的选项排除,在递归中,可以大大提升运行速度。 \n题目举例:洛谷 P1219这道题目是 DFS 中比较经典的八皇后问题。在每行、每列、每个对角线上都只能有一个棋子(皇后)。那么,以下剪枝的几点可以确定: \n\n当这一行放过以后,就开始放下一行,将这一行排除。\n当这一列放过以后,就将这一列打上标记,不再将棋子放在这一列。\n这一个对角线放过后,打上标记,不再将棋子放到对角线上\n\n但是,对角线的标记比较难弄,对角线似乎无法打标记。对角线的标记并不是无解,对角线的 (x,y) 是有规律的。引用原文图片来找规律。先看右斜的对角线有什么规律:可以看到,中间一条蓝色的线对应圈起来的坐标,(3,3) (5,5);靠左一条蓝色的线对应划线的坐标,(4,2) (6,4)。不难看出,$3-3=0=5-5=0; \\hspace{5px} 4-2=2=6-4=2$。可见,同一条右斜对角线上,x 坐标减 y 坐标的绝对值相等。但是相对的对角线上x 坐标减 y 坐标的绝对值也一样,这就比较麻烦。C++ 不能用负数,也不能两条对角线都是同一个标记。老师给了我们一个办法,将他们的差加上 20(别的数也行),问题就解决了。再看左斜的对角线有什么规律:同上,中间一条对应 (2,5) (5,2),左上一条对应 (1,3) (3,1)。与右斜的对角线不同,它们不是差有规律而是和有规律。$2+5=7=5+2=7; \\hspace{5px} 1+3=4=3+1=4$。那么,打标记时将 x+y 作为下标即可。\n加上深搜,代码就出来了:\n#include <cstdio>\nusing namespace std;\n\nbool flagy[50], flagzx[50], flagyx[50];\nint sum = 0;\nint n;\nint s[50];\n\nvoid dfs(int x)\n{\n\tif(x == n + 1)\n\t{\n\t\tsum++ ;\n\t\tif(sum <= 3)\n\t\t{\n\t\t\tfor(int i = 1; i <= n; i++)\n\t\t\t{\n\t\t\t\tprintf(\"%d \", s[i]);\n\t\t\t}\n\t\t\tprintf(\"\\n\");\n\t\t}\n\t\treturn;\n\t}\n\t\n\tfor(int i = 1; i <= n; i++)\n\t{\n\t\tif(flagy[i] == 0 && flagzx[x + i] == 0 && flagyx[x - i + 20] == 0)\n\t\t{\n\t\t\tflagy[i] = 1;\n\t\t\tflagzx[x + i] = 1;\n\t\t\tflagyx[x - i + 20] = 1;\n\t\t\ts[x] = i;\n\t\t\tdfs(x + 1);\n\t\t\tflagy[i] = 0;\n\t\t\tflagzx[x + i] = 0;\n\t\t\tflagyx[x - i + 20] = 0;\n\t\t}\n\t}\n}\n\nint main()\n{\n\tscanf(\"%d\", &n);\n\t\n\tdfs(1);\n\tprintf(\"%d\\n\", sum);\n\n\treturn 0;\n}\n","categories":["CourseNotes"],"tags":["基础算法","递归"]},{"title":"区间最大/小值","url":"//posts/rmq/","content":"求区间最大/小值,即Range maximum/minimum query(RMQ)。可以通过几种方法实现。最简单实现的方法就是直接遍历。假设有 q 次查询,平均每次查询的长度为 n,则时间复杂度为 O(nq)。简单遍历自然是不行的。\n通常用几种更快的方法实现,缺点各不同,如下。(单调栈,ST 表)\n\n\n单调栈定义用栈实现。但是栈中的元素是单调递增或递减的。为了实现,当要压入的元素使栈不再单调递增或递减时,需要将栈顶的元素尽量少地弹出,再将元素压入。如单调递增栈从栈顶到栈底的元素依次为 {1, 3, 6},压入 2,需要先将 1 弹出(为 {2, 3, 6})。再压入 5,需要将 2, 3 都弹出,最终栈为 {5, 6}。\n与 RMQ 的关系:对于栈顶到栈底从小到大的单调递增栈,可以求出第 i 个元素之后第一个大于 i 的元素。\n实现 & 例题对于普通单调栈,实现如下(直接用 STL 给的 stack 了):\nstack<int> stk;\nfor(int i = 1; i <= n; i++)\n{\n int t; scanf(\"%d\", &t);\n while(!stk.empty() && stk.top() < t) // 递增\n {\n // 对于 stk.top() 来说,t 就是长度为 n 的数列中之后第一个大于它的元素\n stk.pop();\n }\n stk.push(t);\n}\n\n例题模板:(洛谷 P5788 单调栈)[https://www.luogu.com.cn/problem/P5788]如下:\n#include <cstdio>\n#include <stack>\nusing namespace std;\n\nstruct node {\n int val, id; // val: 值,id: 编号\n};\nstack<node> stk;\nint n, ans[3000003];\nint main()\n{\n scanf(\"%d\", &n);\n\n for(int i = 1; i <= n; i++)\n {\n node t;\n t.id = i;\n scanf(\"%d\", &t.val);\n if(i == 1) stk.push(t); // 栈空\n else\n {\n if(stk.top().val >= t.val) stk.push(t); // \n else\n {\n while(!stk.empty() && stk.top().val < t.val)\n {\n ans[stk.top().id] = t.id;\n stk.pop();\n }\n stk.push(t);\n }\n }\n }\n\n for(int i = 1; i <= n; i++)\n {\n printf(\"%d \", ans[i]);\n }\n printf(\"\\n\");\n\n return 0;\n}\n\nST 表基于倍增。即以 2 的 x 次方增加求解,可以直接给出答案。但是 ST 表不支持修改操作,即只能求静态区间的最值。这其实就是区间动态规划。\n洛谷 P3865 ST表\n// 1 << j 就是 2 的 j 次方\n#include <cstdio>\n#include <algorithm>\n#include <cmath>\nusing namespace std;\n\nint n, m;\nconst int TMP = 1e5 + 3;\nint st[TMP][20]; // st[i][j] 从 i 开始到 (2^j)-1 的区间最大值\n// ^ 这里只有 20 是因为 log_2^100000 只约为 17,否则开 [TMP][TMP] 在测评机上会 RE(尽管实际没有用那么多)\nint main()\n{\n scanf(\"%d %d\", &n, &m);\n for(int i = 1; i <= n; i++)\n {\n scanf(\"%d\", &st[i][0]);\n }\n\n for(int j = 1; (1 << j) <= n; j++) // 处理\n {\n for(int i = 1; i + (1 << j) - 1 <= n; i++)\n {\n st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);\n }\n }\n\n for(int i = 1; i <= m; i++)\n {\n int l, r;\n scanf(\"%d %d\", &l, &r);\n int tm = log2(r - l + 1);\n printf(\"%d\\n\", max(st[l][tm], st[r - (1 << tm) + 1][tm])); // 两个 max 覆盖了区间\n }\n\n return 0;\n}\n","categories":["CourseNotes"],"tags":["数据结构","动态规划"]},{"title":"一些随笔记录和想法","url":"//posts/seventh-lianbi/","content":"\n .hide-blur {\n filter: blur(5px);\n transition: filter .1s\n }\n .hide-blur:hover {\n filter: none;\n }\n .lianbi-block p:not(.pinyu) {\n text-indent: 2ic;\n line-height: 1.5;\n margin-bottom: .2em;\n }\n .lianbi-block .lianbi-title {\n font-weight: bold;\n text-align: center;\n display: block;\n }\n .lianbi-block .gdg {\n text-decoration: #d60000 wavy underline;\n text-decoration-thickness: 1px;\n }\n .lianbi-block .pinyu {\n color: #d60000;\n font-family: fangsong;\n font-weight: 900;\n line-height: 1.5;\n text-align: right;\n margin: 0 0 1em;\n display: block;\n }\n .lianbi-block#lianbi3 del {\n background-color: #999;\n }\n\n\n写在前面的废话:又是一篇分类于“琐碎”的文章。加上这篇文章,这个分类下才有三篇文章。想来这个博客在运行 hexo init 之初,我就没想过写生活向的文章。如今“琐碎”下,一篇是画,两篇是作文收集,勉强算是透露了点生活向。想想自己喜欢的,是 HTML, CSS, JavaScript 给我带来的样式美化、多样性和自主性。不然我怎么不记到 .txt 里呢?也不知初衷是什么,唉,这样弄又有些喧宾夺主了。多年后看到,也就微微一笑,笑自己写出的东西古怪?吧。\n\n\n下面,红字和红色波浪线都是老师做的标记。红字是批语。红线大概是老师认为有想法的句子吧。按时间排序的文章:\n一记录\n 运动会跳高随记\n 我在操场上观看男子跳高。哪怕只讲跳高场地的气氛也是十分紧张激烈得了。运动员刚跳完,学生裁判和助理就飞快地冲上去把杆子重新架好,再用手推或脚踢将软垫重新压紧实,另一位运动员又跑步向前,一跃而起,脸上带着坚定和紧张。节奏之快,把观众观赛的热情都点燃了。\n 这时,一位跳高运动员吸引了我的注意。根据他服装上的号码牌,我得知他是四班的一名运动员。他起跳了,但没有跳过,带着杆子跌向软垫。我感觉,他脸上写满不甘心与不服输,他和教师裁判要求再跳一遍。于是,我看到他咬紧牙关,再次冲向调高杆,似乎把腿尽可能地抬高,落向软垫——但他又失败了,脚带动杆子滑落下来,杆子落到地上发出哗啦一声。这声音对他来说可能是震天动地的,意味着失败的响声。我看到他再一次向裁判要求重新跳。我从我们班作为跳高裁判助理的施同学口中得知,如果失败,这次起跳是最后一次了。忽地,我联想到了一个人的坚定和决绝,这是不太容易做到的。这一次,他像鸟儿一样轻捷,越过杆子,似乎还有些紧张,但稳稳地落到软垫上,他成功了。\n 我知道,在把杆子升高增加难度后,他最后一次失败了,退出了比追逐战还要激烈的跳高比赛。但能这样重复要求重新跳一次,连续跳三次的精神已经很好了,这是运动员的精神,因而留给我很深的印象。我坐在看台上喝奶茶时想到,尽管他是四班的,而我是三班的人。\n 运动精神感染每一个人.\n\n\n想法这篇文章是开完运动会老师叫写的(废话)。个人认为这届初中运动会不如小学的时候有那种热情、激动、高兴等等开运动会的感觉了。一方面因为延期和运动会时间的缩短,更重要的另一方面运动会项目减少、就在一个操场看、不写通讯稿,对于我来说乐趣都没了。这篇作文 30% 都不算真实感受。至于真实事件,主要是因为有同班同学在跳高场地那儿当助理(也就是扶扶杆子)便跑到那儿和同学聊聊天。男子跳高我们班没人参加,去那儿只为聊天(还顺便当好人帮同学扔橘子皮?)。四班体委跳高,看了看,感觉那种跳了三次的事件适合写到作文里,回家一想到就写了。\n至于老师的评语嘛,原话是这样写的,也不知道老师写这个是不是想到了什么。大概没什么别的深意。\n题外话:那天有奶茶和泡芙供应,只喝了一瓶不知名品牌普通奶茶,味道还好。其他同学吃喝也蛮开心的。\n二记录\n 周五那些琐碎事\n 此刻,我正坐在微格教室里,写这篇随记。自修的时间大家都很安静,只有教室里不知什么设备发出低沉且有规律的“咚咚”声,正是回忆和写作的耗时间,想想在一个半小时之前发生的琐事,我想。\n 下午,五点,同学们乱哄哄地换完了座位,准备排队出校。而包括我的四个人却还要留在学校里,这真的是一种很奇特的感觉,我在独自去往食堂时想到。几个同学吃完了饭回到教室,有打扫一会儿卫生才去微格教室准备上课。来到微格教室,看到教室的布局,让我想到了母校的微格教室,给人一种很宁静的感觉。原本着急上完课,着急回家的心绪也平静下来。在现在想,大概是这种平静才是名词里“素养”与“提升”的感觉,竟有一种置身世外的超然感。\n 这次科学老师上课,居然没有数学课那样急切,没有争分夺秒的感觉,甚至没有往常的节奏快。不知是我心里的主观认为还是微格教室的影响。还是给人一种宁静感,甚至悠闲,但又不是。现在想,那种感觉确实是安心学习的基础啊,不急功近利,而是宁静平和,甚至有限,轻松,太急躁反而容易犯错呢。我想。\n 平时的课一般上一个小时,数学课通常还要拖几分钟,因为任务太多了。但这次不一样,宁静的教室,没有是么非常紧迫的任务。科学老师只上了半个小时的课,刚好在第一节晚自习一半的铃声响起时讲完了作业,剩下的时间老师让我们自修。一切都是平静的。于是,我开始写随记,也就是开头呈现的景象,一切都给人以平静,安静的感觉。\n 想着想着,自修已经到了末尾,班里的同学开始吵闹起来,打破了原有的宁静,以及原有的那份奇特的心境。我意识到,我还要进行枯燥、有条不紊、节奏快的生活。生活的节奏很快,这种宁静是很难再找到了。我想找一句话来总结我的所思所想所写,但周围不再宁静,脑海中全是纷乱的思绪,如同一大片散乱的拼图,十分烦躁。突然几句话拨开纷乱的拼图“夫学须静也,才须学也。”“淫慢则不能励精,险躁则不能治性”。是的,无论干什么,都需要平静,需要宁静专一。宁静,不仅让人放松,还让人奋斗。\n 正准备盖上笔合上本子,却又有一个想法冒出来,我翻到前面看了看开头的自己,抿嘴笑了,添上一句话:\n 从本文开头到结尾,细微的自己变化和涂改状况能看出,这间教室和我在写这篇文章时的宁静与否吧?\n “静能生慧”,一直觉得你是个能“沉得住气”的孩子,很棒哦!\n\n\n想法因为那天开家长会,照理说是所有学生都不用上晚自习五点就回家的。但是我非常遗憾地参加了光明优倍鲜牛奶班培优班,得继续留在学校,上一个小时的培优班,再写作业写到八点晚自习放学。就感觉很离谱,吐槽一下。实际上,我们四个培优班的同学吃完了饭确实受老师委托回到教室打扫卫生,才去的微格教室。平常的课都在二班旁边的普通教室上,这次去微格教室大概是为了不打扰家长会吧。那天语文课和科学课换了,上的是科学课。老师说就讲三十分钟作业,剩下时间自修,就那样做了。我大概写了五十分钟的其他作业,剩下的时间都拿来写上面这篇作文了。当时想不到写什么好,干脆直接写当时场景了。其实也是想在作文里“诉说不易”吧。\n至于老师的评价。。。很难确定老师理解到的是我想写什么,甚至连我自己也不清楚了。也不知道老师为什么评价的字写得比上一篇大。\n附:真不想写“培优班”三个字。\n三地名替换\n原作文中有真实地名,为了避免泄露信息,使用划掉的“占位”进行了替换。显示为这样:占位\n\n\n记录\n 占位旅途记\n 在占位,说起占位岛就让人想起连接占位岛与大陆的那座桥和“海洋”这个字眼来。又是两天后,坐在教室里,细细地回想两到四天前的事。\n 海洋和桥,自然是在路上看到的。从占位海边到占位岛上的路就值得一提。经过(占位占线)和占位占路,我看到了海、海港和船只。基本只在城区待着的我没见过这般略显壮观的景象,立刻被吸引了:一排排轮船靠在岸边,船身下的水些许浑黄,仿佛同船一起睡着。海港在休息,但不减其威严气势,自然很引人注目。和庞大的轮船与广阔无垠的海比起来,“小”客车自然没趣。突然间想到“海洋文化”正是这种意蕴。客车走上山路,越走越高。刹那间,眼前凭空出现一座仿佛横跨海面的桥。我的目光立刻又被桥捉了去。桥的支点是在两边的山上的,桥下面没有柱子支撑,所以看起来仿佛悬浮在海面上,桥也很高。车接近了桥,越发显得小了。还没上桥,就已觉得桥下海之广袤。走在桥上,仿佛神话中天神自由在空中行走,越过海洋。\n 过了桥,便上了岛。又走了一会儿,到了活动基地。基地中发生的事反而没让我想到大海的悠远,似乎没什么特别让人印象深刻的事。海边桥上车中,自我上的一节课更让我印象深刻得多。\n 海的广阔,如广阔得胸怀,容纳着船只和桥。船只和桥也容纳着其他事物。它们相互包容,相对广阔包容相对渺小的。这是海洋的精神:包容。同样地,我们也得学习传承海洋文化精神。想来,这也算是占位之旅给我带来的收获——包容和责任。\n 多去外面走走,会有记很多不一样的启示.\n\n","categories":["Others"],"tags":["作文"]},{"title":"数论:质数筛法","url":"//posts/shaifa/","content":"筛法是快速找出质数的一种方法。平常没有使用任何筛法的的找质数的时间复杂度通常为 $O(\\sqrt n)$,比较慢,但是筛法更快一些。我们学的筛法是埃氏筛和欧拉筛(线性筛)。平常的找质数方法是判断一个数是否能被 1 和它本生以外的数整除,但是筛法的思想不一样。筛法可以说是通常方法的逆向思维,挨个儿寻找当前数的倍数,打上标记,再继续寻找,最后没有被打上标记的就是质数。这种思想的时间复杂度快很多。\n\n\n埃氏筛埃氏筛,全称其实是埃拉托斯特尼筛法 (Eratosthenes)。它的时间复杂度为 $O(n \\log_2 \\log_2 n)$,其实也就是刚才说的方法。这里放一个演示:\n\n 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20     这是初始的表\n \n 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20     2 筛掉了 4 6 8 10 12 14 16 18 20,2 是质数\n \n 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20     3 筛掉了 6 9 12 15 18,3 是质数\n \n 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20     5 筛掉了 10 15 20,其实 5 已经大于 根号 20,剩下的数都是质数,可以退出了,但在这儿继续演示下去\n \n -------------------- break; --------------------     实际循环已经在这儿之前就退出了,但这里继续演示下去\n \n 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20     7 筛掉了 14,7 是质数\n \n 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20     11 13 17 19 的倍数都不在数列中,它们都是倍数\n\n\n最终,筛选出了 2 3 5 7 11 13 17 19 这 8 个质数。 \n埃氏筛的代码也比较简单:\n#include <cstdio>\nusing namespace std;\n\nconst int TEMP = 1e6 + 3; // 需要筛的数字的数量\nint flag[TEMP]; // 记录是否是质数\nvoid is_prime(int n)\n{\n for(int i = 2; i * i <= n; i++) // 和普通的找质数一样\n {\n if(flag[i] == 0) // 找质数的倍数\n {\n for(int j = i * 2; j <= n; j += i) // 从 i * 2 开始是因为不能标记质数,+= i 就是倍数\n {\n flag[j] = 1;\n }\n }\n }\n}\n\nint main()\n{\n int n;\n scanf(\"%d\", &n);\n is_prime(n);\n FILE *fp = freopen(\"./ans.txt\", \"w\", stdout); // 测试文件用,可以注释掉。\n for(int i = 2; i <= n; i++)\n {\n if(flag[i] == 0) // 未被标记过,是质数\n {\n printf(\"%d\\n\", i);\n }\n }\n fclose(fp); // 测试文件用,可以注释掉。\n}\n\n埃氏筛很快,上面数据 1000000 的代码一下就好了。更具体的,可以去看一下 OI Wiki。\n线性筛线性筛也叫欧拉筛,它的出现就是为了找到比埃氏筛还要快的筛法,是由欧拉发现的。在埃氏筛中,一个数可能会被筛很多次,上面的演示也表现出来了。而线性筛每个数只会筛一次,是 $O(n)$ 的时间复杂度。只不过一般来说埃氏筛也够用,一些卡掉埃氏筛的毒瘤数据除外,例如 洛谷 P3383。\n就按照 洛谷 P3383 来,代码是这样的:\n#include <cstdio>\nusing namespace std;\n\nconst int TEMP = 1e8 + 12;\nbool vis[TEMP];\nint pri[TEMP], cnt = 0;\nvoid is_prime(int n)\n{\n for(int i = 2; i <= n; ++i)\n {\n if(!vis[i])\n {\n pri[cnt++] = i;\n }\n for(int j = 0; j < cnt; ++j)\n {\n if(i * pri[j] > n)\n {\n break;\n }\n vis[i * pri[j]] = 1;\n if(i % pri[j] == 0)\n {\n break;\n }\n }\n }\n}\n\n\nint ns, q;\nint ans[TEMP];\nint main()\n{\n scanf(\"%d %d\", &ns, &q);\n is_prime(ns);\n // printf(\"done.\\n\");\n \n // int cnt = 0;\n // for(int i = 2; i <= ns; i++)\n // {\n // if(vis[i] == 0)\n // {\n // printf(\"%d\\n\", i);\n // }\n // }\n \n for(int i = 1; i <= q; i++)\n {\n int temp;\n scanf(\"%d\", &temp);\n printf(\"%d\\n\", pri[temp - 1]);\n }\n \n return 0;\n}\n","categories":["CourseNotes"],"tags":["基础算法","数学"]},{"title":"时间复杂度和空间复杂度","url":"//posts/shijianfuzaduhekongjianfuzadu/","content":"时间复杂度时间复杂度,就是电脑运行一段程序所需要的时间。\n另外,电脑每秒可以运行1e8次。($x$ e $y$代表$x$乘10的$y$次方,即100000000次)\n时间复杂度记作 $O(n)$。\n\n\n普通的时间复杂度(常数时间)记作 $O(1)$,为一段最简单的程序的时间复杂度。\n如以下程序的时间复杂度为 $O(1)$:\n#include <stdio.h>\nusing namespace std ;\n\nint main()\n{\n int n ;\n \n return 0 ;\n}\n\n没错,什么都没有干,只创建了一个变量。\n\n\n\n\n其他时间复杂度(我所知道的很少,只会 $O(n^n)$),有几个循环时间复杂度就为 $n$。\n如以下程序的时间复杂度为 $O(i^2)$\n#include <stdio.h>\nusing namespace std ;\n\nint main()\n{\n for(int i = 0; i < 10; i ++)\n {\n for(int j = 0; j < 10; j ++)\n {\n printf(\"%d\", i) ;\n }\n }\n \n return 0 ;\n}\n\n\n\n老师的练习:\nU262459 数位和3 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)\n注意注意:\n这道题的数据范围很大,($0\\le x \\le10^6$),双重循环直接炸。($O(1000000^2)$)\n所以需要:\n记录详情 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)\n空间复杂度和时间复杂度差不多,只不过这个是和内存有关的。\n不多讲了。就比如:\n#include <stdio.h>\nusing namespace std ;\n\nint main()\n{\n int a[100] ;\n \n return 0 ;\n}\n\n这里,我们定义了一个类型为int的数组,int占4字节,产生的空间就为$4 \\times 1000$字节。\n这就是空间复杂度。\n","categories":["CourseNotes"],"tags":["语言入门"]},{"title":"最短路","url":"//posts/shortest-pth/","content":"最短路即从某一结点到另一结点的路径,使其权值最小。这是一个动态规划问题。\n\n\nFloyd实现Floyd 解决任意两点之间的最短路路径问题,但是图中不能有负环(无向图中不能有负路径权值)。主要实现是确定一个点,如果起始点与结束点经过这个点的距离比原来距离要小,即更新两点间的距离。代码如下:\nint n; // n 为节点数\nfor(int k = 1; k <= n; k++) { // 中转点\n for(int i = 1; i <= n; i++) { // 起\n for(int j = 1; j <= n; j++) { // 终\n dis[i][j] = min(dis[i][k] + dis[k][j], dis[i][j]);\n }\n }\n}\n\n例题洛谷 B3647 Floyd\n#include <cstdio>\nusing namespace std;\n\nint dis[103][103]; // 邻接表和实现数组\nint n, m; // 结点数,边数\nint main()\n{\n scanf(\"%d %d\", &n, &m);\n for(int i = 1; i <= m; i++)\n { // 邻接表存图\n int u, v, w; // u, v 之间有权值为 w 的无向边\n scanf(\"%d %d %d\", &u, &v, &w);\n if(dis[u][v] != 0) { // 判断重边\n if(w < dis[u][v]) {dis[u][v] = w; dis[v][u] = w;}\n continue;\n }\n dis[u][v] = w;\n dis[v][u] = w;\n }\n \n for(int k = 1; k <= n; k++) // Floyd\n {\n for(int i = 1; i <= n; i++)\n {\n for(int j = 1; j <= n; j++)\n {\n if(i == j) continue; // 从节点自己到本身,距离一定为 0\n if(dis[i][k] != 0 && dis[k][j] != 0) // i -> k -> j 路径存在(0 即不存在)\n {\n if(dis[i][k] + dis[k][j] < dis[i][j] || dis[i][j] == 0) dis[i][j] = dis[i][k] + dis[k][j]; // 转移方程\n }\n }\n }\n }\n for(int i = 1; i <= n; i++)\n {\n for(int j = 1; j <= n; j++)\n {\n printf(\"%d \", dis[i][j]);\n }\n printf(\"\\n\");\n }\n \n return 0;\n}\n\nSPFA 代码(仅记录)#define typec int\nconst typec INF = 0x3f3f3f3f;\nstruct Edge\n{\n int v;\n int cost;\n Edge(int _v = 0, int _cost = 0) : v(_v), cost(_cost) {}\n};\nvector<Edge> E[MAXN];\nvoid addedge(int u, int v, int w)\n{\n E[u].push_back(Edge(v, w));\n}\nbool vis[MAXN]; // 在队列标志\nint cnt[MAXN]; // 每个点的入队列次数\ntypec dis[MAXN];\nbool SPFA(int start, int n)\n{ // 附带判定负环\n for (int i = 1; i <= n; i++)\n {\n dis[i] = INF;\n }\n vis[start] = true;\n dis[start] = 0;\n queue<int> que;\n que.push(start);\n cnt[start] = 1;\n while (!que.empty())\n {\n int u = que.front();\n que.pop();\n vis[u] = false;\n for (int i = 0; i < E[u].size(); i++)\n {\n int v = E[u][i].v;\n if (dis[v] > dis[u] + E[u][i].cost)\n {\n dis[v] = dis[u] + E[u][i].cost;\n if (!vis[v])\n {\n vis[v] = true;\n que.push(v);\n if (++cnt[v] > n)\n return false;\n // cnt[i] 为入队列次数,判定是否存在负环\n }\n }\n }\n }\n return true;\n}\n\nDijkstra实现迪克斯特拉(?),用于求单源最短路(只从一个结点出发到另一结点的最短路径),不可以有负权值的边。\n使用优先队列优化的 dijkstra 步骤:\n\n设从起点到编号为 i 的结点的最短路为 dis[i],设起点编号为 s。初始时 dis[s] = 0; 其他的均为无穷大。\n优先队列(pq) 里存该遍历的结点,排序方案为按结点的最短路大小(dis[结点])排序。\n将 pq 顶部元素弹出,是前一个点。如果从起点到 前一个点(top) 的最短路(dis[top]) 加上到 这个点(tv) 的边的权值(tw) 小于 从起点到这个点的最短路(dis[tv]) ,则执行 dis[tv] = dis[top] + tw,将 {tv, dis[tv]}(结构体) 加入 pq。\n\n重复第 3 步,直到 pq 为空。\n代码P4779 单源最短路径\n#include <cstdio>\n#include <vector>\n#include <queue>\nusing namespace std;\n\nconst int TMP = 2e5 + 3;\nstruct edge\n{\n int v, w;\n};\nstruct node\n{\n int id, dis;\n bool operator< (const node &x) const\n {\n return x.dis < dis;\n }\n};\nint n, m, s, dis[TMP], vis[TMP];\nvector<edge> gr[TMP];\npriority_queue<node> pq;\nint main()\n{\n scanf(\"%d %d %d\", &n, &m, &s);\n for(int i = 1; i <= m; i++)\n {\n int v, u, w;\n scanf(\"%d %d %d\", &u, &v, &w); // u --w--> v\n edge tmp;\n tmp.v = v; tmp.w = w;\n gr[u].push_back(tmp);\n }\n \n for(int i = 1; i <= n; i++)\n {\n dis[i] = 0x7fffffff;\n }\n dis[s] = 0;\n node tmp; tmp.id = s; tmp.dis = 0;\n pq.push(tmp);\n while(!pq.empty())\n {\n node top = pq.top();\n pq.pop();\n if(!vis[top.id])\n {\n vis[top.id] = 1;\n for(int i = 0; i < gr[top.id].size(); i++)\n {\n int tv = gr[top.id][i].v, tw = gr[top.id][i].w;\n if(dis[top.id] + tw < dis[tv])\n {\n dis[tv] = dis[top.id] + tw;\n node ptmp; ptmp.id = tv; ptmp.dis = dis[tv];\n pq.push(ptmp);\n }\n }\n }\n }\n for(int i = 1; i <= n; i++)\n {\n printf(\"%d \", dis[i]);\n }\n \n return 0;\n}\n\n演示设 起点(s) 为 1,INF 代表无穷大,上方注释 c 表示这次访问并改变了,v 表示仅访问过。如下:\n\ndis[4] = {0, INF, INF, INF};\nvis[4] = {0, 0, 0, 0};\npq = {{.id=1, .dis=0}};\n\n\n\n// c c c\ndis[4] = {0, 2, 5, 4};\nvis[4] = {1, 0, 0, 0};\npq = {{.id=2, .dis=2}, {.id=4, .dis=4}, {.id=3, .dis=5}};\n\n\n\n// c c\ndis[4] = {0, 2, 4, 3};\nvis[4] = {1, 1, 0, 0};\npq = {{.id=4, .dis=3}, {.id=4, .dis=4}, {.id=3, .dis=4}, {.id=3, .dis=5}};\n\n\n\n// v v\ndis[4] = {0, 2, 4, 3};\nvis[4] = {1, 1, 1, 1};\npq = {};\n\n","categories":["CourseNotes"],"tags":["动态规划","图论"]},{"title":"数据结构:队列和栈","url":"//posts/stackandqueue/","content":"队列和栈都是线性数据结构,它们一个是先进先出,一个是先进后出,有着不同的使用场景。这两个数据结构基于链表,也可以用数组模拟这样的数据结构,通过 C++ 中 STL 提供的容器也可以更加方便快捷地实现。\n队列队列 (queue) 是在一端插入另一段删除的线性表,遵循先进先出,类似于排队,可以称为先进先出 (FIFO) 表。队列中,允许入队 (enqueue) 的一端为队尾,允许出队 (dequeue) 的一端为队头。以后的广度优先搜索就会用到它。\n\n\n数组模拟队列使用数组模拟队列需要一个存储数据的数组,同时用变量标记队头和队尾。假设队列数组名为 q,头指针为 ql,尾指针为 qr,则:\n插入元素时,需要将队尾加上 1,假设元素为 x。结果:q[++qr] = x;;删除元素时,需要将队头指向下一个元素,由于这不是链表,直接执行即可。结果:ql++;;访问队首,直接 q[ql];;访问队尾,直接 q[qr];;清空队列时,头指针尾指针初始化,ql = 1; qr = 0;。\n可见,数组模拟队列和数组模拟链表的缺点一样,内存不是动态分配的。这导致若数据过大则内存可能超出限制,若比数组的大小还大那就越界了,队列就溢出了。\n队列的溢出但由于数组是直接将队首队尾加来加去,可能会有队列(数组)前面还空着,但是队列溢出的情况这就叫做假溢出。若假溢出则需要使用循环队列,也就是说当尾指针超出数组,则将这一个元素从数组的开头放起。当然,若是真的全部存完了那有用的数据也会覆盖掉,这就是真溢出了。\nSTL queueSTL 提供的容器 queue,需要引入 <queue> 头文件。通过模板,定义形式是这样:queue<[value type]> name。成员函数的使用:\n\nfront() 返回队首值。\nback() 返回队尾值。\npush([value]) 元素入队。\npop() 元素出队。\nempty() 返回布尔值,表示队列是否为空。\nsize() 返回数值,表示队列里元素的数量。\n\n容器不会假溢出,但是若队列为空还要 pop() 就会溢出。\n栈栈 (stack)是在同一端插入同一端弹出的表。元素可插入弹出的一段称为栈顶,另一端是栈底,遵循先进后出。\nSTL stack 容器需要引入 <stack> 头文件。成员函数有:top() 返回栈顶值push([value]) 插入pop() 弹出empty() 是否为空栈size() 返回元素数量\n同样的,容器没有上限,不会上溢出。但是若栈已空还要 pop() 就会造成下溢出。\n\n附:visualgo 演示: \n\n栈 https://visualgo.net/en/list?slide=4\n队列 https://visualgo.net/en/list?slide=5\n\n","categories":["CourseNotes"],"tags":["队列","数据结构"]},{"title":"排序","url":"//posts/sort/","content":"一些说在前面的要点\n稳定性\n在我们以下学过的排序算法中,只有选择排序和快速排序不是稳定的。\n稳定性,就是有两个相同的数字,在排序后两个数字的相对位置不变。(前面的在前面,后面的在后面)\n\n\n逆序对\n前面的一个数字大于后面一个数字,这就叫做逆序对。\n例如 $5\\ 1\\ 2\\ 3\\ 4$ 中,有 $4$ 对逆序对。\n\n\n\n\n\n选择排序简介选择排序,顾名思义,就是选出所有元素中最小的元素,然后再放到前面。这个排序非常好理解,但是,时间复杂度为 $O(n^2)$ ,数据一大就要炸了。( $n$ 为数组长度)\n我们可以使用一个变量来记录其中一个最小数的下标,然后再进行第一个数与最小的数的交换。由于不断地将最小的数往前放,最终完成排序。但由于第 $i$ 次遍历之后,第 $i$ 个元素就是最小的元素,因此由 $i + 1$ 个元素开始判断。\n例子例如有这样一个数组:\n8 \\ 5 \\ 7 \\ 9 \\ 2 \\ 6\n\n遍历后得知 $2$ 是最小的,与第一个元素 $8$ 进行交换。\n{\\color{red}2} \\ 5 \\ 7 \\ 9 \\ {\\color{red}8} \\ 6\n\n以此类推:\n{\\color{green}2} \\ {\\color{red}5} \\ 7 \\ 9 \\ 8 \\ 6 \\\\\n{\\color{green}2} \\ {\\color{green}5} \\ {\\color{red}6} \\ 9 \\ 8 \\ {\\color{red}7} \\\\\n{\\color{green}2} \\ {\\color{green}5} \\ {\\color{green}6} \\ {\\color{red}7} \\ 8 \\ {\\color{red}9} \\\\\n{\\color{green}2} \\ {\\color{green}5} \\ {\\color{green}6} \\ {\\color{green}7} \\ {\\color{green}8} \\ 9 \\\\\n{\\color{green}2} \\ {\\color{green}5} \\ {\\color{green}6} \\ {\\color{green}7} \\ {\\color{green}8} \\ {\\color{green}9} \\\\\n\n示例程序#include <cstdio>\nusing namespace std ;\n\nint n, a[3002] ; \nint main()\n{\n scanf(\"%d\", &n) ;\n for(int i = 1; i <= n; i ++)\n {\n scanf(\"%d\", &a[i]) ;\n }\n \n for(int i = 1; i <= n - 1; i ++)\n {\n int th = i ;\n for(int j = i; j <= n; j ++)\n {\n if(a[th] > a[j])\n {\n th = j ;\n }\n }\n int temp = a[i] ;\n a[i] = a[th] ;\n a[th] = temp ;\n }\n \n // printf(\"\\n\"); \n for(int i = 1; i <= n; i ++){printf(\"%d \", a[i]) ;} \n printf(\"\\n\") ;\n \n return 0 ;\n}\n\n演示网址演示网址:https://visualgo.net/zh/sorting?slide=8\n冒泡排序简介冒泡排序,将前面一个元素和后面一个元素做对比,若前面的元素大于后面的元素即进行交换。时间复杂度也为 $O(n^2)$。\n由于不断地将前一个元素大于后一个元素的一组交换,假设数组中有 $n$ 个元素,第 $i$ 次遍历后,第 $n$ 个元素就是最大的数,因此下一次遍历由 $i$ 至 $n - i$ 。\n例子还是上面的那个例子,利用冒泡排序:\n8 \\ 5 \\ 7 \\ 9 \\ 2 \\ 6 \\ \\\\ \\\\\n{\\color{red}5} \\ {\\color{red}8} \\ 7 \\ 9 \\ 2 \\ 6 \\ \\\\\n5 \\ {\\color{red}7} \\ {\\color{red}8} \\ 9 \\ 2 \\ 6 \\ \\\\\n5 \\ 7 \\ {\\color{red}8} \\ {\\color{red}9} \\ 2 \\ 6 \\ \\\\\n5 \\ 7 \\ 8 \\ {\\color{red}2} \\ {\\color{red}9} \\ 6 \\ \\\\\n5 \\ 7 \\ 8 \\ 2 \\ {\\color{red}6} \\ {\\color{red}9} \\ \\\\\n{\\color{red}5} \\ {\\color{red}7} \\ 8 \\ 2 \\ 6 \\ {\\color{green}9} \\ \\\\\n5 \\ {\\color{red}7} \\ {\\color{red}8} \\ 2 \\ 6 \\ {\\color{green}9} \\ \\\\\n5 \\ 7 \\ {\\color{red}2} \\ {\\color{red}8} \\ 6 \\ {\\color{green}9} \\ \\\\\n5 \\ 7 \\ 2 \\ {\\color{red}6} \\ {\\color{red}8} \\ {\\color{green}9} \\ \\\\\n{\\color{red}5} \\ {\\color{red}7} \\ 2 \\ 6 \\ {\\color{green}8} \\ {\\color{green}9} \\ \\\\\n5 \\ {\\color{red}2} \\ {\\color{red}7} \\ 6 \\ {\\color{green}8} \\ {\\color{green}9} \\ \\\\\n5 \\ 2 \\ {\\color{red}6} \\ {\\color{red}7} \\ {\\color{green}8} \\ {\\color{green}9} \\ \\\\\n{\\color{red}2} \\ {\\color{red}5} \\ 6 \\ {\\color{green}7} \\ {\\color{green}8} \\ {\\color{green}9} \\ \\\\\n2 \\ {\\color{red}5} \\ {\\color{red}6} \\ {\\color{green}7} \\ {\\color{green}8} \\ {\\color{green}9} \\ \\\\\n{\\color{red}2} \\ {\\color{red}5} \\ {\\color{green}6} \\ {\\color{green}7} \\ {\\color{green}8} \\ {\\color{green}9} \\ \\\\\n{\\color{green}2} \\ {\\color{green}5} \\ {\\color{green}6} \\ {\\color{green}7} \\ {\\color{green}8} \\ {\\color{green}9} \\ \\\\\n\n示例程序#include <cstdio>\nusing namespace std ;\n\nint n, a[3002] ; \nint main()\n{\n scanf(\"%d\", &n) ;\n for(int i = 1; i <= n; i ++)\n {\n scanf(\"%d\", &a[i]) ;\n }\n \n for(int j = 1; j <= n - 1; j ++)\n {\n for(int i = 1; i <= n - j; i ++)\n {\n if(a[i] > a[i + 1])\n {\n int t = a[i] ;\n a[i] = a[i + 1] ;\n a[i + 1] = t ;\n }\n }\n }\n \n \n // printf(\"\\n\"); \n for(int i = 1; i <= n; i ++){printf(\"%d \", a[i]) ;} \n \n return 0 ;\n}\n\n演示网址演示网址:https://visualgo.net/zh/sorting?slide=7\n插入排序简介插入排序,是在冒泡排序的基础上做的改进。它将整个数列分为两个部分:已排序的与未排序的。\n由于一个数本身就没有任何顺序,所以我们可以假设元素 $a[1]$ (假设 $1$ 为数组第一个元素)就是一个已经排列好的数列。随后,将 $a[2]$ 插入进已排序好的数列中。若 $a[2] > a[1]$ 则不交换,否则则交换。这就是一个循环的过程。插入进已排列好的数列中时,这个比较就是冒泡排序的过程:\n\n将前面一个元素和后面一个元素做对比,若前面的元素大于后面的元素即进行交换。\n\n例子依然是前面那个样例,在这里,我们假设有一个空间是已排序空序列,另一个是未排序序列。\n\\boxed{\n\\begin{aligned}\n&\\text{说明:} \\\\\n&? \\ \\text{代表未排序序列} \\\\\n&! \\ \\text{代表已排序序列} \\\\\n\\end{aligned}\n}\n\n\n\\begin{aligned}\n? \\ &8 \\ 5 \\ 7 \\ 9 \\ 2 \\ 6 \\ \\\\\n! \\ &[Empty] \\\\ \\\\\n? \\ &5 \\ 7 \\ 9 \\ 2 \\ 6 \\ \\\\\n! \\ &{\\color{yellow}8} \\\\ \\\\\n? \\ &7 \\ 9 \\ 2 \\ 6 \\ \\\\\n! \\ &{\\color{yellow}8} \\ {\\color{yellow}5} \\\\ \\\\\n? \\ &7 \\ 9 \\ 2 \\ 6 \\ \\\\\n! \\ &{\\color{yellow}5} \\ {\\color{yellow}8} \\\\ \\\\\n? \\ &9 \\ 2 \\ 6 \\ \\\\\n! \\ &{\\color{yellow}5} \\ {\\color{yellow}8} \\ {\\color{yellow}7} \\\\ \\\\\n? \\ &9 \\ 2 \\ 6 \\ \\\\\n! \\ &{\\color{yellow}5} \\ {\\color{yellow}7} \\ {\\color{yellow}8} \\\\ \\\\\n&...(\\text{不再详细演示})\n\\end{aligned}\n\n\n示例程序#include <cstdio>\nusing namespace std ;\n\nint a[100010] ;\nint main()\n{\n int n ;\n scanf(\"%d\", &n) ;\n for(int i = 1; i <= n; i ++)\n {\n scanf(\"%d\", &a[i]) ;\n }\n \n for(int j = 1; j <= n - 1; j ++)\n {\n for(int i = j; i >= 1; i --)\n {\n if(a[i + 1] < a[i])\n {\n int t = a[i] ;\n a[i] = a[i + 1] ;\n a[i + 1] = t ;\n }\n else\n {\n break ;\n }\n }\n }\n \n for(int i = 1; i <= n; i ++)\n {\n printf(\"%d \", a[i]) ;\n }\n printf(\"\\n\") ;\n \n return 0 ;\n}\n\n可以看到,它比普通冒泡排序快在当前一个元素大于等于后一个元素时会退出循环。但最坏的情况还是 $O(n^2)$ (也是平均情况),因为如果他时一个倒序序列的话,这样排序每次都要从头到尾比较一遍,这和冒泡排序是一样的。\n演示网址演示网址:https://visualgo.net/zh/sorting?slide=9\n计数排序简介计数排序需要用到前缀和的知识,简单来说就是将每一个数字出现的次数记录到一个数组中(这里称为计数数组),然后再按照这个计数数组将答案数组赋值好。还是比较好理解的。只是也许没有冒泡排序来的码量小。计数排序适用于排序数据量较大的排序,但数字不能过大。如果数字上限很高的话,计数排序就无能为力了,因为数组不能开太大,否则内存不够用。同时,最好不要有负数,要不然计数数组要开两倍大,虽说也可以通过处理达到“负下标”的效果,但还是上面说的三个排序比较好。\n例子假设有这样一个数组 $a$ :\n8 \\ 8 \\ 8 \\ 5 \\ 5 \\ 7 \\ 5 \\ 2 \\ 2 \\ 6 \\ 6 \\ 2\n\n统计结果是这样的:$8$ 出现了 3 次, $5$ 出现了 3 次, $7$ 出现了 1 次, $6$ 出现了 2 次, $2$ 出现了 3 次。\n那么,就可以将这些数按顺序赋值到答案数组中,然后再输出答案数组。在此处不演示了。手都敲酸了\n示例程序#include <cstdio>\nusing namespace std ;\n\nconst int TEMP = 1e7 + 10 ;\nint a[TEMP] ;\nint b[TEMP], c[TEMP], d[TEMP] ;\nint main()\n{\n int n ;\n scanf(\"%d\", &n) ;\n for(int i = 1; i <= n; i ++)\n {\n scanf(\"%d\", &a[i]) ;\n b[a[i]] ++ ;\n }\n \n c[0] = b[0] ;\n for(int i = 1; i <= 10000000; i ++)\n {\n c[i] = c[i - 1] + b[i] ;\n }\n \n for(int i = 1; i <= c[0]; i ++)\n {\n d[i] = 0 ;\n }\n for(int i = 1; i <= 10000000; i ++)\n {\n int l = c[i - 1] + 1 ;\n int r = c[i - 1] + b[i] ;\n for(int j = l; j <= r; j ++)\n {\n d[j] = i ;\n }\n }\n \n for(int i = 1; i <= n; i ++)\n {\n printf(\"%d\\n\", d[i]) ;\n }\n \n return 0 ;\n}\n\n演示网址演示网址:https://visualgo.net/zh/sorting?slide=15\n归并排序简介归并排序其实是分治的思想,将一个数列分成两份,再分,直至每个数列的长度都为一为止。然后再将每一个数列按照大小放回数组里。时间复杂度为 $O(n \\log_{2}{n})$,和上面的几个排序比较,已经很好了。 \n例子一个数列 $8 \\ 5 \\ 7 \\ 9 \\ 2 \\ 6$ 的归并排序:\n\n示例程序#include <cstdio>\nusing namespace std;\n\nconst int TEMP = 5e5 + 3;\nint a[TEMP], b[TEMP];\nlong long sum = 0;\n\nvoid dg(int l, int r)\n{\n int mid = (l + r) / 2;\n if(l != r)\n {\n dg(l, mid);\n dg(mid + 1, r);\n }\n \n int l1 = l, l2 = mid + 1;\n int cnt = l;\n while(l1 <= mid && l2 <= r)\n {\n if(a[l1] >= a[l2])\n {\n b[cnt] = a[l2];\n if(a[l1] == a[l2]) sum += mid - l1;\n else sum += mid - l1 + 1;\n l2++ ;\n }\n else\n {\n b[cnt] = a[l1];\n l1++ ;\n }\n cnt++ ;\n }\n \n while(l1 <= mid)\n {\n b[cnt] = a[l1];\n l1++ ;\n cnt++ ;\n }\n while(l2 <= r)\n {\n b[cnt] = a[l2];\n l2++ ;\n cnt++ ;\n }\n \n for(int i = l; i <= r; i++)\n {\n a[i] = b[i];\n }\n}\n\nint n;\nint main()\n{\n scanf(\"%d\", &n);\n for(int i = 1; i <= n; i++)\n {\n scanf(\"%d\", &a[i]);\n }\n \n dg(1, n);\n printf(\"%lld\\n\", sum);\n return 0;\n}\n\n演示网址演示网址:https://visualgo.net/en/sorting?slide=11\n快速排序简介“快速”和快速排序的名字没有关系。快速排序的实现就是寻找一个中间数(类似于二分的 mid),确保左边的最大值比中间数小,右边的最小值比中间数大。即将数列分成两部分,左边的所有数大于右边。\n注意\n快速排序是不稳定的,它的时间复杂度自然也不稳定。平均时间复杂度为 $O(n \\log n)$,但是最差可退化到 $O(n^2)$。\n\n\n示例程序#include <cstdio>\n#include <algorithm>\nusing namespace std;\n\nconst int TMP = 5e6 + 3;\nint a[TMP], n;\n\nvoid qsort(int l, int r)\n{\n if(l >= r) return;\n int i = l, j = r, flag = a[l + r >> 1]; // flag: 中间数\n while(i <= j)\n {\n while(a[i] < flag) i++; // 让左区间增大\n while(a[j] > flag) j--; // 让右区间增大\n if(i <= j)\n {\n swap(a[i], a[j]); // 交换是左边小于右边\n i++; j--;\n }\n }\n qsort(l, i - 1); // 分段递归\n qsort(i, r);\n}\n\nint main()\n{\n scanf(\"%d\", &n);\n for(int i = 0; i < n; i++)\n {\n scanf(\"%d\", &a[i]);\n }\n qsort(0, n - 1);\n\n for(int i = 0; i < n; i++)\n {\n printf(\"%d \", a[i]);\n }\n \n return 0;\n}\n\n<algorithm> 头文件 sort() 排序这么多排序算法,头都要晕了。为什么不用别人现成的函数来排序呢?看, C++ 就有一个超级好用的头文件 -> <algorithm> ,用它里面的 sort() 函数就可以啦!并且,它支持自定义排序。\n从大到小排序时,还有一个更方便的方法:\nsort(a, a + 10, greater<int>());\n\n两个重载:\ntemplate<typename _RandomAccessIterator>\n inline void\n sort(_RandomAccessIterator __first, _RandomAccessIterator __last)\n\ntemplate<typename _RandomAccessIterator, typename _Compare>\n inline void\n sort(_RandomAccessIterator __first, _RandomAccessIterator __last,\n\t _Compare __comp)\n\n想要用的时候,就直接 sort(&a[0], &a[n]) 或者 sort(a, a + n) 就可以了。写排序函数 cmp(x, y) 时两个参数代表数组里的元素可以理解为 a[i - 1], a[i],升序(默认)写做 return x > y。\n","categories":["CourseNotes"],"tags":["基础算法","优化","排序"]},{"title":"scanf 和 printf 的格式符","url":"//posts/stdoi-snf-pnt/","content":"又是一个随记,方便自己的使用。C++ 中的 scanf 和 printf 其实有很多比 cin cout 好用的地方,放在这里。\n\n\nscanf 的使用读入的格式就直接上表格吧,先把一些特定的读入格式符放在这儿:\n整数小数其他\n\n\n格式符\n用途\n\n\n\n%d\n读入 int 整型\n\n\n%ld\n读入 long 整型\n\n\n%lld\n读入 long long 整型\n\n\n%hd\n读入 short 整型\n\n\n%u\n读入 unsigned int 整型\n\n\n%lu\n读入 unsigned long 整型\n\n\n%llu\n读入 unsigned long long 整型\n\n\n\n\n格式符\n用途\n\n\n\n%f\n读入 float 类型\n\n\n%lf\n读入 double 类型\n\n\n%Lf\n读入 long double 类型\n\n\n\n\n格式符\n用途\n\n\n\n%c\n读入 char 类型\n\n\n%s\n读入字符串,也就是 char 数组\n\n\n%o\n读入八进制整型\n\n\n%x\n读入十六进制整型\n\n\n\n以上其实都是一些读入的格式。还有一些能让读入的格式更加丰富的格式化。\n限制位数在以上的任何格式符的 % 后面加上数字 n,即读入的位数就是 n。例如:\nint a, b;\nscanf(\"%1d %1d\", &a, &b);\nprintf(\"%d %d\\n\", a, b);\n\n假设输入 10345 则会输出 1 0。\n只读入,不赋值在任何格式符的 % 后加上 *,就不会赋值给任何变量。例如:\nint a;\nlong long b;\nshort c;\nscanf(\"%d %*lld %lld %hd\", &a, &b, &c);\nprintf(\"%d %lld %hd\", a, b, c);\n\n假设输入:\n2147483647 4294967295 11415612712638 128\n\n则输出:\n2147483647 11415612712638 128\n\n可见,scanf 忽略了第二个数字 4294967295。\nprintf 的使用其实它和 scanf 差不多。但是有多了精度,对齐什么的。\n标识和宽度%[标识][宽度]\n\n宽度其实和上面一样,只不过默认右对齐。标识就可以更改。\n\n\n\n标识\n作用\n\n\n\n-\n将宽度的数字左对齐\n\n\n+\n正数显示正号\n\n\n#\n和 %o 带有八进制前缀 0,和 %x 带有十六进制前缀 0x\n\n\n0\n将宽度的空格变成 0\n\n\n例如:\nint a, b, c, d, e, f;\nscanf(\"%d %d %d %d %d %d\", &a, &b, &c, &d, &e, &f);\nprintf(\"|%010d|%10d|%-10d|\\n\", a, b, c); // |往左填零 |宽度为十 |靠左 |\nprintf(\"|%+10d|%#10o|%#10x|\\n\", d, e, f); // |若正数显示正号|更改为八进制,有0前缀|更改为十六进制,有0x前缀|\n// 宽度全部为十。\n\n假设输入:\n19283 1983 1283 12873 83287 7283\n\n则会输出:\n|0000019283| 1983|1283 |\n| +12873| 0242527| 0x1c73|\n\n精度用于小数,用 .n 标识保留 n 为小数。例如:\ndouble p;\nscanf(\"%lf\", &p);\nprintf(\"%.3lf\\n\", p); // 保留三位小数\n\n假设输入 114514.1919810 会输出 114514.192。\n","categories":["Programming"],"tags":["语言入门"]},{"title":"C++类(结构体)","url":"//posts/struct-class/","content":"结构体结构体使用 struct 关键字定义。对于目前的我来说,没什么要记的。例如:\nstruct test {\n int val, num;\n}a;\na.val;\n\n\n\n需要记的是:当使用该类型的指针变量时,访问其子元素应使用 -> 而不是直接使用 .,也可以通过解地址(但比较麻烦)的写法:(*a).val。例子:\nstruct test {\n int val;\n};\ntest a, *b, c; // b 是指针变量\n\nb = &a;\na.val = 114;\nprintf(\"%d %d %d\\n\", a.val, b->val, (*b).val); // 因为 b 存储 a 的地址,又 `b->val` 和 `(*b).val` 相同,输出为:\n// 114 114 114\n\nc.val = 810;\nb = &c;\nprintf(\"%d %d %d\\n\", c.val, b->val, (*b).val); // 同上解释。输出为:\n// 810 810 810\n\nb->val = 70; // 相当于 `(*b).val=70;`。由于 b 存储 c 的地址,c 的 val 的值被改变了。\nb = &a;\n(*b).val = 80; // 同上,相当于 `b->val=80;`。b 存 a 的地址,a.val 被改变了。\nprintf(\"%d %d %d\", a.val, c.val, b->val); // b 还指向 a,a, c 分别被改为 80, 70。则输出为:\n// 80 70 80\n\n\n创建一个类类类似于结构体,只不过他是C++独有的而已。对于我这个入门者来说,类几乎等于结构体,只不过他出现了一种概念:公有 public 私有 private 受保护的 protected 。当然,对于我来说,除了 public 能用,其他的都不能用。我还是太蒻了。捂脸.jpg 但是继承类似乎可以用。例如:\nclass myclass\n{\n public :\n int a ;\n private :\n int b ;\n protected :\n int c ;\n}\n\n对于我来说,除了 a 都不能用。好像也没啥能记的了。再次捂脸.jpg\n","categories":["CourseNotes"],"tags":["语言入门"]},{"title":"样式过渡动画","url":"//posts/trast-styl/","content":"一些可以用到 :hover 状态上的样式过渡。\n以下的效果主要是通过 伪元素 实现的。感觉麻烦,直接加上 transition: all .4s ease-in-out。把下面那些记在这儿是方便自己以后用。“感觉麻烦…”这句话也是给自己看的(逃\n\n\n下划线这里的样式过渡适用于从无下划线到有下划线的样式过渡。\n淡入淡出这里更改下划线的颜色,或者说是 border-bottom-color。因为直接设置过渡 border 不会有效果。\n\n .post-body span.egunlcolor {\n border-bottom: 1px solid transparent;\n cursor: pointer;\n transition: border-bottom-color .2s;\n }\n .post-body span.egunlcolor:hover {\n border-bottom-color: #555;\n }\n\n\n代码比较简单:\nspan { /* 这里的选择器改成要用的元素,所有的状态、颜色和数值也是,下同 */\n border-bottom: 1px solid transparent;\n transition: border-bottom-color .2s;\n}\nspan:hover {\n border-bottom-color: #555;\n}\n\n鼠标放在这里,效果就是这个样子\n从某个方向出现这里更改下划线(伪元素)的长度,或者说是 transform:scaleX()。\n\n .post-body div.egunderline span {\n margin-bottom: 5px;\n cursor: pointer;\n position: relative;\n }\n .post-body div.egunderline span::before {\n content: '';\n position: absolute;\n bottom: 0;\n left: 0;\n height: 2px;\n width: 100%;\n background-color: #555;\n transform: scaleX(0);\n transform-origin: inherit;\n transition: transform .2s;\n }\n .post-body div.egunderline span:hover::before {\n transform: scaleX(1);\n }\n .post-body div.egunderline span#egleftirighto:hover::before {\n transform-origin: left;\n }\n\n\n代码是这样的:\nspan {\n margin-bottom: 5px;\n position: relative;\n}\nspan::before {\n content: '';\n position: absolute;\n bottom: 0;\n left: 0;\n height: 2px;\n width: 100%;\n background-color: #555;\n transform: scaleX(0);\n transform-origin: right; /* , center, left */ /* 更改这里可以把动画的位置改变 */\n transition: transform .2s;\n}\nspan:hover::before {\n transform: scaleX(1);\n /* transform-origin: left; */ /* 上一处设为 right 这里设为 left 有左进右出的效果 */\n}\n\n效果:\n\n 下划线从左出现:transform-origin:left;\n 下划线从中间出现:transform-origin:right;\n 下划线从右出现:transform-origin:right;\n\n\n还可以把未触发状态下的 transform-origin 与触发状态下的值分别改成 left 和 right,像这样子:\n左边出现,右边消失 的效果\n\n下划线上升到背景色比较适用于链接。其实是通过 box-shadow 的 inset 和 y偏移量 实现的。\n代码是:\n.post-body span.egultobg {\n box-shadow: inset 0px -1px 0 0 #555;\n transition: box-shadow .2s, color .2s;\n}\n.post-body span.egultobg:hover {\n box-shadow: inset 0px -1lh 0 0 #555; /* 注意:lh 单位一些浏览器还不支持 */\n color: #eee;\n}\n\n\n .post-body span.egultobg {\n box-shadow: inset 0px -1px 0 0 #555;\n transition: box-shadow .2s, color .2s;\n cursor: pointer;\n }\n .post-body span.egultobg:hover {\n box-shadow: inset 0px -1.5em 0 0 #555;\n color: #eee;\n }\n\n\n下划线上升为背景色\n模拟按钮就是加上一些 box-shadow 和 padding 啦。代码:\nbutton.egprsbtn {\n --egprs-bgcolor: #fff;\n --egprs-color: #555;\n --egprs-hvbgc: #eee;\n --egprs-btnhei: 4px;\n cursor: pointer;\n padding: 15px 15px calc(15px + var(--egprs-btnhei)) 15px;\n border-radius: 8px;\n border: 2px solid var(--egprs-color);\n color: var(--egprs-color);\n background-color: var(--egprs-bgcolor);\n box-shadow: 0 var(--egprs-btnhei) 0 0 var(--egprs-color);\n transition: transform .2s, color .2s, box-shadow .2s, background-color .2s;\n}\nbutton.egprsbtn:hover {\n background-color: var(--egprs-hvbgc);\n}\nbutton.egprsbtn:active {\n color: var(--egprs-bgcolor);\n background-color: var(--egprs-color);\n box-shadow: none;\n transform: translateY(var(--egprs-btnhei))\n}\n\n\n button.egprsbtn {\n --egprs-bgcolor: #fff;\n --egprs-color: #555;\n --egprs-hvbgc: #eee;\n --egprs-btnhei: 4px;\n cursor: pointer;\n padding: 15px 15px calc(15px + var(--egprs-btnhei)) 15px;\n border-radius: 8px;\n border: 2px solid var(--egprs-color);\n color: var(--egprs-color);\n background-color: var(--egprs-bgcolor);\n box-shadow: 0 var(--egprs-btnhei) 0 0 var(--egprs-color);\n transition: transform .2s, color .2s, box-shadow .2s, background-color .2s;\n }\n button.egprsbtn:hover {\n background-color: var(--egprs-hvbgc);\n }\n button.egprsbtn:active {\n color: var(--egprs-bgcolor);\n background-color: var(--egprs-color);\n box-shadow: none;\n transform: translateY(var(--egprs-btnhei))\n }\n\n\n按钮的样式\n","categories":["Programming"],"tags":["CSS"]},{"title":"双指针-快慢指针","url":"//posts/two-pointers/","content":"\n双指针其实不是真正的指针,而是有两个变量在序列上进行一些操作。——Lqingyi(Lxandqi)\n\n思想分类\n普通双指针 也就是两个普通的 for (也可以是其他的)循环嵌套。\n左右指针 其实就是二分搜索,一个变量指向开头,一个变量指向末尾,根据条件向中间遍历,直到指针相遇或满足某种条件。(也就是逼近答案)\n快慢指针 两个指针(变量)开始同时开头,但一个遍历的快,一个慢,直到条件满足或指针到末尾。\n\n\n\n提示双指针是二重循环。\n一般来说,快慢指针的第二个指针(快指针)的变量是在 for 循环体之外定义的。因为 for 循环会初始化变量。快慢指针的变量是不可以初始化的,因为已经遍历过的就不用遍历了,再遍历一遍就变成普通双指针(暴力枚举)了。\n怎么感觉只有例题才能讲清楚???\n例题给定一个长度为 $n$ 的整数序列 $a_1,a_2,…,a_n$ 以及一个长度为 $m$ 的整数序列 $b_1,b_2,…,b_m$。请你判断 $a$ 序列是否为 $b$ 序列的子序列。子序列指序列的一部分项按原有次序排列而得的序列,例如序列 $a_1,a_3,a_5$ 是序列 $a_1,a_2,a_3,a_4,a_5$ 的一个子序列。\n输入时:第一行包含两个整数 $n,m$。第二行包含 $n$ 个整数,表示 $a_1,a_2,…,a_n$。第三行包含 $m$ 个整数,表示 $b_1,b_2,…,b_m$。输出时:如果 $a$ 序列是 $b$ 序列的子序列,输出一行 Yes。否则,输出 No。\n数据保证:$1 \\le n \\le m \\le 10^5$$−10^9 \\le a_i,b_i \\le 10^9$\n\n解题:数据那么大,暴枚肯定不行。那么就用今天学的双指针。一个指针 $i$ 遍历数组 $a$ 的元素,指针 $j$ 遍历数组 $b$ 的元素。写一个 while(1) 死循环, $j$ 在 while 中每次 ++,如果 $a_i = b_j$ ,则 break ,如果 $j > m$ 则输出 No 。 $j$ 变量(指针)的定义要写在循环之外。\n代码: \n#include <cstdio>\nusing namespace std;\n\nint n, m ;\nconst int N = 1e5 + 5 ;\nint a[N], b[N] ;\nint main()\n{\n scanf(\"%d %d\", &n, &m) ;\n for(int i = 1; i <= n; i++) scanf(\"%d\", &a[i]) ;\n for(int i = 1; i <= m; i++) scanf(\"%d\", &b[i]) ;\n \n int j = 1 ;\n for(int i = 1; i <= n; i++)\n {\n while(1)\n {\n if(j > m)\n {\n printf(\"No\\n\") ;\n return 0 ;\n }\n if(a[i] == b[j])\n {\n j++ ;\n break ;\n }\n j++ ;\n }\n }\n \n printf(\"Yes\\n\") ;\n \n return 0 ;\n}","categories":["CourseNotes"],"tags":["基础算法","解题思想"]},{"title":"Waline 评论加入记录","url":"//posts/waline-set/","content":"看了看“归档”页面才发现自己没有在 2024 发布过文章。最近寒假作业写累了(思维导图太烦啦),就更新了下留言板,从 giscus 换成了 waline,不用登录就可以留言了,管理也更方便。 \n\n\n服务端按照官方文档,我注册了 LeanCloud 国际版 账号(华东或华北节点需要备案域名,可我没有钱买域名)。随后点击“创建应用”,填写应用名称(计费方式选择“开发版”)。创建应用完毕后依次点击 设置 -> 应用凭证,三个 KEY 等下要使用。\n随后我选择了 DetaSpace 部署。注册账号,随后下载 Waline 应用,点击“Install on Space”等待完成。随后返回首页,点击底部任务栏 deta 图标呼出菜单,依次点击 Add Card to Horizon -> Installed App -> Waline。鼠标悬浮到新增加的卡片上,点击灰色的 deta 图标点击 “Open Settings”,点击“Configuration”选项卡,将 LeanCloud 中 AppID, AppKey, MasterKay 依次加入到 LEAN_ID, LEAN_KEY, LEAN_MASTER_KEY 中。滑动到底部点击“Save Changes”。服务端完成。\n可以通过“Configuration”中 GRAVATAR_STR 更改用户默认头像。更改 DISABLE_REGION DISABLE_USERAGENT 为 true 隐藏评论下方用户代理和位置。\n鼠标悬浮到卡片上,点击右上角“Waline”及其徽标,在 url 后增加 /ui/register,也就是 https://waline-x-xxxxxxx.deta.app/ui/register,注册一个账号成为管理员,就可以点击“管理”选项卡管理用户和评论了。\n直接使用\n不需要 LeanCloud,直接将 Waline 部署到 DetaSpace 上,Waline 会自动将数据存储到 DetaBase 里。点击灰色的 deta 图标,选择 View App Data -> Base 选项卡,这里就是 Waline 直接存储的数据。\n\n\n客户端通过 CDN 引入 waline.js 和 waline.css,在想加入评论框的页面引入。新增 script 标签,可参考:\nimport '/comments/waline.js';\n \nWaline.init({\n el: \"#waline\",\n path: location.pathname,\n lang: \"zh-CN\",\n serverURL: \"https://yoursite.deta.app\",\n locale: { /* 自己更改 */\n admin: \"管理员\",\n login: \"管理员登录\",\n placeholder: \"友善的评论会收获更多美好\",\n },\n});","categories":["Programming"],"tags":["JavaScript"]},{"title":"一些六年级的小练笔","url":"//posts/xiaolianbi/","content":"一些六年级写的小作文(小练笔),留作纪念。\n主题:点面结合写“体检”300字以上同学们紧张地等待着,一年一度的体检开始了。与往常不同的是,这次体检要抽血。验血区里,紧张的气氛非常明显。我一拿到条形码(当时验血有一个条形码,用来验证身份和当作标签),就直奔验血区,怕时间长了自己会害怕。很快就轮到了我。我卷起袖子,将胳膊往桌上一放,马上扭过头闭上眼,不敢看针扎进我的胳膊里的样子。医生开始抹酒精了,我觉得我一直在发抖。等了一会儿,我感到一丝疼痛,但很快就没有了。只听见软管松开的声音。“好了。”旁边的同学提醒我。我睁开眼,看见医生正在往试管上贴标签,我长舒一口气。幸好一次性就完成了。旁边同学问我:“疼吗?”我按紧棉花,苦笑道:“都一样”。另外一边儿正在测血压,气氛很轻松,毕竟同学们习惯测血压了。她卷起袖子,医生帮她把仪器连接好,按下了“开始”键。只见那块连接仪器的布不断收缩、绷紧,仪器上的数字不断跳动、变化,然后定格,布又松弛了。医生在表格上龙飞凤舞地写下一行数字,又开始忙其他的了。各种各样的检查结束了,同学们兴高采烈地走回了教室,如释重负。\n主题:未知老师一走,原本安静的班级马上热闹起来,比十个菜市场还吵。调皮大王马上抓住这一时机,放声大吼起一首歌“大河向东流啊……”一些同学被调皮大王逗得哈哈大笑,根本就不去写作业本了,也愉快地聊起天来。大组长,班长怎么管也没用。调皮大王更起劲了,拿起书本卷成筒状,凑到同学耳边大吼大唱。许多“小调皮”被吸引了,也毫无顾忌地转来转去,和旁边的同学愉快地大声聊天。随着调皮大王“嘿”地一声大吼,一位女同学差点儿被吓哭。跑操铃声想起,新任体育委员地调皮大王又来了兴致,催促吵吵闹闹的班级马上出来,在外面排好了歪歪扭扭的队伍。“小调皮”们在教室外还是安静不下来,直至别的班的老师过来,那吵吵闹闹的一群人才一哄而散。\n主题:点面结合 大扫除 400字很快,一盆盆水端了进来,同学们都拿着工具,对教室里脏的地方发动了“攻击”。同学们忘乎所以地忙碌着,仿佛这个世界上只有他手中的事情了。他拿着一把扫帚,卖力地扫角落里一个落满灰尘的夹缝。地上那些明显的纸片都被他扫走了,他扫门后面,灰尘弥漫,但他手握扫帚,用力去扫角落的墙灰,很快,门后干干净净。这里的角落有一个柜子,他俯下身子,仔细地查看着柜子后面的灰尘,发现灰尘有一尺多高。他马上把扫帚探进柜子后面,半个身子差点儿都倒下去了,艰难的把一丁点儿灰尘扫了出来。他不甘心,拿了把小扫帚,用力搬开了柜子,手拿小扫帚,将所有灰尘都扫了出来。他还觉得不够干净,干脆直接上手,地上几块顽固的胶带就被他死了下来,丢进了灰尘堆。垃圾桶里虽然没有什么大型垃圾,灰尘却堆成了小山,这大部分是他的功劳。她拿着一块湿抹布,握着小铲刀,擦着墙上的污垢和双面胶带。瓷砖已经有些发黑了,还有一些水笔的字迹,被摘除的布告后留下双面胶的痕迹。她不慌不忙,从容地擦着那些水笔的痕迹。手所滑过的地方,一切都亮了起来。但也有非常顽固的污垢,她用手指盖着布,上上下下用力地擦。扣。污垢撑不住,很快,墙上的污点完全没有了。双面胶牢牢地粘在墙上,经过长时间的风化,又硬又黏。她先用湿布擦那些胶,这样用铲子更好铲,“斩草除根”后,再用力擦一擦,墙上的双面胶一点也没有了。结白明亮的瓷砖倒映着她满意的微笑。经过每一个同学的努力,教室里焕然一新。每个同学露出的,都是满意的微笑。\n","categories":["Others"],"tags":["作文"]},{"title":"一些有用的东西有没有用的东西","url":"//posts/yixieyouyongdedongxiyoumei/","content":"宏定义和类型定义宏定义,将一个指令导向另一个指令。宏定义属于预处理指令,使用规范为:\n#define [标识符] [常量]\n\n\n\n与变量不同的是:宏定义可以理解为把一个文本替换成另一个文本。比如说:\n#include <cstdio>\n#define a 3 + 2\nusing namespace std ;\n\nint main()\n{\n printf(\"%d\", a * 3) ;\n \n return 0 ;\n}\n\n和\n#include <cstdio>\nusing namespace std ;\n\nint main()\n{\n int a = 3 + 2 ;\n printf(\"%d\", a * 3) ;\n \n return 0 ;\n}\n\n使用了 #define 的输出 $9$ ,使用了 int 的输出 $15$ 。这是因为 #define 只是把 $a$ 替换为了 $3+2$ ,因此先算 $2 \\times 3=6$ ,再算 $6+3=9$ ,而 int 将 $a$ 定为 $5$ ,再算 $5 \\times 3=15$ 。从这些角度来看,define 只是一个“替换”的操作。\n\n类型定义,将变量类型导向简单的标识符。类型定义属于语句,使用规范为:\ntypedef [变量类型] [标识符] ;\n\n与 #define 不同的是:#define 属于预处理指令,需要在预处理器处理。而且 #define 可以将任何指令或字符指向标识符;而 typedef 属于语句,需要在编译器中编译。且 typedef 只支持将变量类型指向标识符。例如:\n#include <cstdio>\n#define pnt printf // printf可以写成pnt\n#define scn scanf // scanf可以写成scn\nusing namespace std ;\n\nint main()\n{\n typedef long long ll ; // long long可以写成ll\n // typedef scanf scn ; -> 这句话是错误的\n ll a ; // 定义了变量a,类型为long long\n scn(\"%lld\", &a) ;\n pnt(\"%lld\", a + 1) ;\n \n return 0 ;\n}\n\n常量无论是 int 还是 double 类型,他们都是变量,即随时可以改变。当想用到不用改变的量时,就要用到常量了。\n定义一个常量很简单,只需要在变量类型前加上一个 const 就行了。例如:\n#include <cstdio>\nusing namespace std;\n\nconst int a = 100 ;\nint b[a] = {0, 1, 2} ;\nint main()\n{\n for(int i = 0; i < 100; i ++)\n {\n printf(\"%d\\n\", b[i]) ;\n }\n \n return 0 ;\n}\n\n在这里,我们建立了一个名为 $a$ 的常量,其值为 $100$ 。随后定义一个名为 $b$ 的数组,大小为 $a$ , $a=100$ , $b$ 数组的大小就是 $100$ 。\n一般来说对于我来说,常量的作用不算特别大,但是拿来定义数组却很方便。\n有符号和无符号有符号, signed ,符号即为 $-$ ,在一般的数据定义中,都是有符号的类型。无符号, unsigned ,即没有复数,正数数据范围会大很多。比如普通的 int ( signed int )数据范围是 $-2147483648 \\sim 2147483647$ 而无符号 int ( unsigned int )数据范围是 $0 \\sim 4294967295$ ,这是因为 unsigned int 将附属部分拿来存正数部分,所以可以存的正数会大很多。\n在变量类型中,只有 signed 会被认作 signed int ;只有 unsigned 也会被认作 unsigned int ;不带 signed 和 unsigned 的任何数据类型都会被认做有符号 signed 数据类型。因此,在某些时候主函数 int main() 也可以写成 signed main() \n读写数据当数据很大时,直接从控制台输入是不可能的。同样地,控制台输出数据过多也不方便比较数据。\n#include <stdio.h>\nusing namespace std;\n\nint main()\n{\n FILE *fpi = freopen(\"test.in\", \"r\", stdin);\n FILE *fpo = freopen(\"test.out\", \"w\", stdout);\n\n fclose(fpi);\n fclose(fpo);\n return 0;\n}\n\nfc test.out test.ans\ncode -d test.out test.ans\n","categories":["CourseNotes"],"tags":["语言入门"]},{"title":"指针","url":"//posts/zhizhen/","content":"指针的作用指针,顾名思义,就是指向某一东西的标志。\n当我们想存地址时,就可以使用指针变量:\nint a ;\nint *b = &a ;\n\nb就是一个指针变量,存着a的地址。\n\n\n普通指针普通指针就是上面的例子中的b,它存着a的地址,因此&a和b的输出结果是一样的。\n存指针地址的指针当我们想存指针地址时那该怎么办呢?\n很简单,嵌套:\n#include <cstdio>\nusing namespace std ;\n\nint main()\n{\n int a ;\n int *b = &a ;\n int **c = &b ;\n \n printf(\"%d %d %d\", &a, b, c) ;\n \n return 0 ;\n}\n\n这样就不会出错了。\n取地址和解地址*既可以是乘号,又可以是解地址,还可以是指针变量;\n&既可以是按位与运算,又可以是取地址。\n解地址,与取地址相反,就是按照地址去找地址里存的数。\n我们可以使用指针变量解地址得到原本的数,例如:\n#include <cstdio>\nusing namespace std ;\n\nint main()\n{\n int a = 10 ;\n int *b = &a ;\n int **c = &b ;\n \n printf(\"%d %d %d\", a, *b, **c) ;\n \n return 0 ;\n}\n\n这段代码输出的应该是三个同样10,我们一个一个来:\n\na,就是a本身,输出10;\n*b,就是解地址b,等于解地址&a,等于a,输出10;\n**c,就是解地址c,等于解地址&b,等于解地址&a,等于a,输出10。\n\n画个图更清晰:\n**c=*(&b) → b=*(&a) → a = 10 → printf(10)\n因此,解地址也是可以嵌套的。\n指针数组和数组指针指针数组,用来存指针的数组;\n数组指针,数组的首地址。\n我们使用时*a[100]代表数组a的地址,因为数组的优先级更高。弄一个指针数组应该写(*a)[100]。\n同样的,a代表数组中第一个元素的首地址,&a代表整个数组的首地址,我们是不是还可以这样写?\n#include <cstdio>\nusing namespace std ;\n\nint main()\n{\n int a[100] = {1, 2, 3, 4} ;\n printf(\"%d %d %d %d\", *(a + 0), *(a + 1), *(a + 2), *(a + 3)) ;\n \n return 0 ;\n}\n\n输出1 2 3 4,这与a[n]的效果是一样的。\n因此,我们得出结论:数组的本质就是指针。\n","categories":["CourseNotes"],"tags":["语言入门","指针"]},{"title":"最小生成树","url":"//posts/min-span-tree/","content":"前置概念生成树 即从一个连通图中选择结点数减一条边构成一个树。最小生成树,即所有生成树中边权和最小。\n\n\nKruskal克鲁斯卡尔(?好像这么译),使用贪心。它的思想就是从小到大加入边,同时避免形成环(使用并查集判断两点是否联通【是否在同一集合中】,这是树的要求)。当加入的边数为结点数减一时,就可以退出了。\n详见 P3366 最小生成树 的代码和注释。\n#include <cstdio>\n#include <algorithm>\nusing namespace std;\n\nconst int TMP = 5e5 + 3;\nint fa[TMP]/*并查集*/, n/*结点数*/, m/*边数*/, ans;\n\nstruct node\n{\n int x, y, z; // x --z--> y\n} a[TMP];\nbool cmp(node a, node b)\n{\n return a.z < b.z; // 按照边权从小到达排序(思想)\n}\n\n// 并查集模板:这里只使用路径压缩\nvoid init() // 初始化\n{\n for(int i = 1; i <= n; i++)\n {\n fa[i] = i;\n }\n}\nint find(int num) // 寻找其所在集合根节点\n{\n if(fa[num] != num) fa[num] = find(fa[num]);\n return fa[num];\n}\nvoid merge(int x, int y) // 合并\n{\n fa[find(fa[x])] = find(fa[y]);\n}\n\nint main()\n{\n scanf(\"%d %d\", &n, &m);\n init();\n for(int i = 1; i <= m; i++)\n {\n scanf(\"%d %d %d\", &a[i].x, &a[i].y, &a[i].z);\n }\n sort(a + 1, a + m + 1, cmp); // 按照边权从小到达排序\n \n int cnt = 0; // 加入的边数\n for(int i = 1; i <= m; i++)\n {\n if(cnt == n - 1) // 已经是生成树了(加入的边为 n - 1),不用再找\n {\n break;\n }\n if(find(a[i].x) != find(a[i].y)) // 判断 x, y 是否联通(在同一集合中),避免形成环\n {\n merge(a[i].x, a[i].y); // 如果不在同一集合中,就合并(加入了边)\n ++cnt;\n ans += a[i].z;\n }\n }\n if(cnt >= n - 1) printf(\"%d\\n\", ans);\n else printf(\"orz\\n\"); // 加入的边数不到 n - 1,生成的不是树\n \n return 0;\n}\n\nPrim与 Kruskal 不同,它的思想是加点,类似于 Dijkstra\n\n堆优化的方式类似 Dijkstra 的堆优化,但如果使用二叉堆等不支持 O(1) decrease-key 的堆,复杂度就不优于 Kruskal,常数也比 Kruskal 大。所以,一般情况下都使用 Kruskal 算法,在稠密图尤其是完全图上,暴力 Prim 的复杂度比 Kruskal 优,但不一定实际跑得更快。OI-Wiki\n\n主要原因是老师没具体说,所以也不知道代码怎么写。所以就偷懒不写了。\n","categories":["CourseNotes"],"tags":["树","图论"]}]