diff --git "a/2023\345\271\264\344\270\255\351\227\262\350\201\212/index.html" "b/2023\345\271\264\344\270\255\351\227\262\350\201\212/index.html"
index 11d6613..29c57b3 100644
--- "a/2023\345\271\264\344\270\255\351\227\262\350\201\212/index.html"
+++ "b/2023\345\271\264\344\270\255\351\227\262\350\201\212/index.html"
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/HelloWorld/index.html b/HelloWorld/index.html
index d15a8c7..c037ef3 100644
--- a/HelloWorld/index.html
+++ b/HelloWorld/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/about/index.html b/about/index.html
index f1109a9..c58ec04 100644
--- a/about/index.html
+++ b/about/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
@@ -581,13 +581,16 @@
- 将两个集合合并
@@ -799,6 +801,7 @@ 并查集
p[find(a)] = find(b);
d[find(a)] = distance;
【模版】并查集
+食物链
并查集题单
堆
如何手写一个堆?
diff --git a/algorithm-dp/index.html b/algorithm-dp/index.html
index b3b92aa..a386196 100644
--- a/algorithm-dp/index.html
+++ b/algorithm-dp/index.html
@@ -49,7 +49,7 @@
content="2023-09-13">
+ content="2023-11-06">
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
@@ -496,7 +496,7 @@
- 1.5k 字
+ 1.9k 字
@@ -528,9 +528,14 @@ 线性DP朴素:O(n2)
f[i]表示以第i个数结尾的序列中上升子序列长度的最大值
遍历ai所有可能的前一个数aj(aj<ai且0≤j≤i−1)
-f[i]=max(f[j]+1),j∈[0,i−1]
+f[i]=max(f[j]+1,f[i]),j∈[0,i−1]
如果要保存最长序列:g[i]保存从哪一步j转移过来
+代码:https://www.luogu.com.cn/record/124595657
优化:O(nlogn)
+用一个q数组储存长度为i的序列的结尾数字的最小值
+可以证明qi>qi−1>...>q2>q1,即数组严格单调递增
+对于ai,二分找到最大的qk<=ai,f[i]=k+1,更新qk=ai
+代码:https://www.luogu.com.cn/record/133704642
最长公共子序列
朴素:O(n2)
f[i][j]表示所有在第一个序列的前i个字母中出现,且在第二个序列的前j个字母中出现的子序列的最大值
@@ -542,6 +547,12 @@ 线性DPf[i,j]=max(f[i−1,j],f[i,j−1])
当a[i]==b[j]时,f[i,j]=max(f[i,j],f[i−1,j−1]+1)
优化:O(nlogn)
+编辑距离
+f[i,j]所有将a[1−i]变成b[1−j]的操作方式的最小步数
+区间划分,①删除最后一个数、②增加最后一个数、③修改最后一个数
+① f[i−1,j]+1
+②f[i,j−1]+1
+③f[i−1,j−1]+1 (如果a[i]==b[j]则不需要加一,即不需要进行修改操作)
区间DP
石子合并
f[i,j]表示将第i堆石子到第j堆石子合并成一堆石子的方式的代价最小值/最大值
@@ -604,7 +615,16 @@
然后是(2,2)的最长距离,如果没有记忆化,那么搜索过程为:(2,2)->(2,1)->(1,1)
但是(2,1)之前已经搜过了,再去搜就是浪费时间,之前搜索已经知道(2,1)的值为2,那么搜索过程就是缩短为:(2,2)->(2,1),即为3
杂题
+【动态规划1】动态规划的引入
kkksc03考前临时抱佛脚
+【NOIP1999】挖地雷
+dp+递归倒序输出,当然这题使用爆搜也可以过
+最大字段和
+f[i]表示i所在的有效序列的最大字段和
+如果f[i−1]+a[i]更大,则加入到前一个有效序列
+否则a[i]自己为一个有效序列
+五倍经验日
+01背包的变形
diff --git a/algorithm-greedy/index.html b/algorithm-greedy/index.html
index fe7e82a..8b88d8c 100644
--- a/algorithm-greedy/index.html
+++ b/algorithm-greedy/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/algorithm-high-precision/index.html b/algorithm-high-precision/index.html
index b6b2c5d..e096cc0 100644
--- a/algorithm-high-precision/index.html
+++ b/algorithm-high-precision/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/algorithm-knapsack-problem/index.html b/algorithm-knapsack-problem/index.html
index b37470c..1348922 100644
--- a/algorithm-knapsack-problem/index.html
+++ b/algorithm-knapsack-problem/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/algorithm-math/index.html b/algorithm-math/index.html
index 2a3b3c4..8b79274 100644
--- a/algorithm-math/index.html
+++ b/algorithm-math/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/algorithm-minimum-spanning-tree/index.html b/algorithm-minimum-spanning-tree/index.html
index 2a71f52..ec8c8dd 100644
--- a/algorithm-minimum-spanning-tree/index.html
+++ b/algorithm-minimum-spanning-tree/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/algorithm-prefix-sum-and-difference/index.html b/algorithm-prefix-sum-and-difference/index.html
index 2d02548..2f5cd02 100644
--- a/algorithm-prefix-sum-and-difference/index.html
+++ b/algorithm-prefix-sum-and-difference/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/algorithm-search/index.html b/algorithm-search/index.html
index 7d6bb69..4065372 100644
--- a/algorithm-search/index.html
+++ b/algorithm-search/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/algorithm-shortest-path/index.html b/algorithm-shortest-path/index.html
index 88c4545..f00613d 100644
--- a/algorithm-shortest-path/index.html
+++ b/algorithm-shortest-path/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/algorithm-skills/index.html b/algorithm-skills/index.html
index 76b0015..e4dd3f1 100644
--- a/algorithm-skills/index.html
+++ b/algorithm-skills/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/algorithm-sort/index.html b/algorithm-sort/index.html
index 39df16c..ec3c8b2 100644
--- a/algorithm-sort/index.html
+++ b/algorithm-sort/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/algorithm-two-divided/index.html b/algorithm-two-divided/index.html
index cd37ad9..44a14e3 100644
--- a/algorithm-two-divided/index.html
+++ b/algorithm-two-divided/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/archives/2023/03/index.html b/archives/2023/03/index.html
index aa88ed9..655517d 100644
--- a/archives/2023/03/index.html
+++ b/archives/2023/03/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/archives/2023/04/index.html b/archives/2023/04/index.html
index 120468c..55a7ada 100644
--- a/archives/2023/04/index.html
+++ b/archives/2023/04/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/archives/2023/05/index.html b/archives/2023/05/index.html
index 79d2baa..55be34a 100644
--- a/archives/2023/05/index.html
+++ b/archives/2023/05/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/archives/2023/06/index.html b/archives/2023/06/index.html
index ddd28a1..2cb8474 100644
--- a/archives/2023/06/index.html
+++ b/archives/2023/06/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/archives/2023/07/index.html b/archives/2023/07/index.html
index e16325f..db61200 100644
--- a/archives/2023/07/index.html
+++ b/archives/2023/07/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/archives/2023/08/index.html b/archives/2023/08/index.html
index 8238cb5..1b53f1a 100644
--- a/archives/2023/08/index.html
+++ b/archives/2023/08/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/archives/2023/09/index.html b/archives/2023/09/index.html
index 5471329..4a59a13 100644
--- a/archives/2023/09/index.html
+++ b/archives/2023/09/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/archives/2023/10/index.html b/archives/2023/10/index.html
index f8e93f8..83b5442 100644
--- a/archives/2023/10/index.html
+++ b/archives/2023/10/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/archives/2023/index.html b/archives/2023/index.html
index dd176dd..d4512b6 100644
--- a/archives/2023/index.html
+++ b/archives/2023/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/archives/2023/page/2/index.html b/archives/2023/page/2/index.html
index ccf8c68..3b8d35b 100644
--- a/archives/2023/page/2/index.html
+++ b/archives/2023/page/2/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/archives/index.html b/archives/index.html
index e4eea59..76cbf42 100644
--- a/archives/index.html
+++ b/archives/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/archives/page/2/index.html b/archives/page/2/index.html
index f4538c5..4cea9bc 100644
--- a/archives/page/2/index.html
+++ b/archives/page/2/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/binary/index.html b/binary/index.html
index 04cc8fe..3001232 100644
--- a/binary/index.html
+++ b/binary/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/categories/index.html b/categories/index.html
index a3ac585..bf8120b 100644
--- a/categories/index.html
+++ b/categories/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
@@ -584,10 +584,10 @@
- 前端
+ 论文
共计 1 篇文章
@@ -595,10 +595,10 @@
- 论文
+ 前端
共计 1 篇文章
diff --git "a/categories/\345\211\215\347\253\257/index.html" "b/categories/\345\211\215\347\253\257/index.html"
index 4d96b7b..c348375 100644
--- "a/categories/\345\211\215\347\253\257/index.html"
+++ "b/categories/\345\211\215\347\253\257/index.html"
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git "a/categories/\347\256\227\346\263\225/index.html" "b/categories/\347\256\227\346\263\225/index.html"
index 73e26dd..662b172 100644
--- "a/categories/\347\256\227\346\263\225/index.html"
+++ "b/categories/\347\256\227\346\263\225/index.html"
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git "a/categories/\347\256\227\346\263\225/page/2/index.html" "b/categories/\347\256\227\346\263\225/page/2/index.html"
index 68abcc0..75e7b51 100644
--- "a/categories/\347\256\227\346\263\225/page/2/index.html"
+++ "b/categories/\347\256\227\346\263\225/page/2/index.html"
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git "a/categories/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/index.html" "b/categories/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/index.html"
index 111cdaf..0163972 100644
--- "a/categories/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/index.html"
+++ "b/categories/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/index.html"
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git "a/categories/\350\256\272\346\226\207/index.html" "b/categories/\350\256\272\346\226\207/index.html"
index 1d2c87d..c0f4ae1 100644
--- "a/categories/\350\256\272\346\226\207/index.html"
+++ "b/categories/\350\256\272\346\226\207/index.html"
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git "a/categories/\351\227\262\350\201\212/index.html" "b/categories/\351\227\262\350\201\212/index.html"
index 0a0621f..9956443 100644
--- "a/categories/\351\227\262\350\201\212/index.html"
+++ "b/categories/\351\227\262\350\201\212/index.html"
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/index.html b/index.html
index 6b4cfe1..2f6fce0 100644
--- a/index.html
+++ b/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/links/index.html b/links/index.html
index 16db2a5..9fb7970 100644
--- a/links/index.html
+++ b/links/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/log/index.html b/log/index.html
index 89e69e4..fa9f6a0 100644
--- a/log/index.html
+++ b/log/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/page/2/index.html b/page/2/index.html
index 142a4f7..daa0509 100644
--- a/page/2/index.html
+++ b/page/2/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/paper-deep-learning-detect-BOD5/index.html b/paper-deep-learning-detect-BOD5/index.html
index 1d1d9c4..6395f0e 100644
--- a/paper-deep-learning-detect-BOD5/index.html
+++ b/paper-deep-learning-detect-BOD5/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/search.json b/search.json
index 769b5fa..03adffe 100644
--- a/search.json
+++ b/search.json
@@ -1 +1 @@
-[{"title":"二进制相关","url":"/binary/","content":"计算机以二进制表示数据,以表示电路中的正反。在二进制下,一个位只有0和1。逢二进一位。\n计算机中存储数据,以字节为单位,一个字节有8个位,即可以表示-128~127范围的数字。\n 基础运算\n\n与\n\n用符号&表示,运算规律是:真真为真,真假为假,假假为假(一假即假)\n1&1 //1\n1&0 //0\n0&0 //0\n\n或\n\n用符号|表示,运算规律是:真真为真,真假为真,假假为假(一真即真)\n1|1 //1\n1|0 //1\n0|0 //0\n\n非\n\n运算符为~,取反的逻辑,运算规律:二进制位若为1,取反后为0。若为0,取反后为1\n~1 //11111110\n\n左移\n\n将二进制数向左移位操作,高位溢出则丢弃,低位补0\na=11;\na<<1;\n移位前:0000 1011\n移位后:0001 0110(十进制值为22)\n对一个数左移1位就是乘以2,左移n位就是乘以2的n次方(而左移运算比乘法快得多)\n\n右移\n\n右移位运算中,无符号数和有符号数的运算并不相同。对于无符号数,右移之后高位补0;对于有符号数,符号位一起移动,正数高位补0,负数高位补1\n无符号数\na=16;\na>>3;\n移位前:0001 0000\n移位后:0000 0010(十进制值为2)\n有符号数(正数)\nb=32;\na>>3;\n移位前:0010 0000\n移位后:0000 0100(十进制值位4)\n有符号数(负数)\nb=-32;\nb>>3;\n移位前:1010 0000\n移位后:1000 0100(十进制值为-4)\nc=25;\nc>>4;\n移位前:0001 1001\n移位后:0000 0001(十进制值为1)\n实际上,我们发现。右移n位就是除以2的n次方,当得到的商不是整数时会往小取整\n 正数、负数的表示\n对于负数的表示,比较特殊,其有一条运算规律:对非负数的二进制进行取反、然后+1,便可得其负数的二进制表示,以3为例子:\n00000011//3的二进制\n//~00000011 对3进行取反,得到结果\n11111100\n//对结果进行+1\n11111101//-3在计算机中的最终表示\n最终得出在一个字节下,11111101表示-3\n","categories":["计算机基础"],"tags":[]},{"title":"由数据范围反推时间复杂度及算法","url":"/algorithm-skills/","content":"一般竞赛时间限制在1s或2s,所以时间复杂度尽量控制在107−10810^7 - 10^8107−108\n下面给出在不同数据范围下,代码的时间复杂度和算法如何选择:\n\nn≤30n \\le 30n≤30,dfs+剪枝、状态压缩dp\nn≤100n \\le 100n≤100,O(n3)O(n^3)O(n3),Floyd、dp、高斯消元\nn≤1000n \\le 1000n≤1000,O(n2)O(n^2)O(n2),dp、二分、朴素Dijsktra、朴素Prim、Bellman-Ford\nn≤10000n \\le 10000n≤10000,O(nn)O(n \\sqrt n)O(nn),块状链表、分块、莫队\nn≤105n \\le 10^5n≤105,O(nlogn)O(nlogn)O(nlogn),sort、线段树、树状数组、set/map、heap、拓扑排序、堆优化Dijkstra、堆优化Prim、Kruskal、spfa、二分、CDQ分治、整体二分、后缀数组\nn≤106n \\le 10^6n≤106,O(n)O(n)O(n),单调队列、hash、双指针、BFS、并查集、kmp、AC自动机;常数比较小的O(nlogn)O(nlogn)O(nlogn),sort、树状数组、heap、dijkstra、prim\nn≤107n \\le 10^7n≤107,O(n)O(n)O(n),双指针扫描、Kmp、AC自动机、线性筛素数\nn≤109n \\le 10^9n≤109,O(n)O(\\sqrt n)O(n),判断质数\nn≤1018n \\le 10^{18}n≤1018,O(nlogn)O(nlogn)O(nlogn),最大公约数、快速幂、数位dp\nn≤101000n \\le 10^{1000}n≤101000,O((logn)2)O((logn)^2)O((logn)2),高精度加减乘除\n\n一些常见数据类型的大小\n\nlong long 内的最大阶乘 20!\nint 内的最大阶乘 12!\nint => 2312^{31}231,2∗1092*10^92∗109\nlong long => 2632^{63}263,9∗10189*10^{18}9∗1018\nfloat => 38位\ndouble => 308位\n\nmemset常赋值:-1,0,0x3f,-0x3f\n无穷大:0x3f3f3f3f\n","categories":["算法"],"tags":[]},{"title":"基于深度学习的城市港口水体BOD5稀疏矩阵软检测","url":"/paper-deep-learning-detect-BOD5/","content":"Soft detection of 5-day BOD with sparse matrix in city harbor water using deep learning techniques\n 概要\n\t对于像纽约这样的沿海城市,如何更好地控制和管理港口水质是一项重要的任务。为了实现这样的需求,管理者们和政府需要追踪关键的水质指标,例如温度、pH值和溶解氧。在这些指标当中,五日生化需氧量BOD5BOD_5BOD5是一个需要花费许多时间与精力来检测的重要指标,无论在学术上还是在工程上都造成了很大不便。现有的实验和统计方法无法有效解决检测时间问题且提供的精度有限。 此外,由于各种人为操作失误与设备误差,用于BODBODBOD检测和预测的数据包含许多缺失值,导致矩阵稀疏。很少有研究在开发统计检测方法时解决稀疏矩阵问题。为了解决这些误差,我们提出了一种基于深度学习的模型,该模型结合了深度矩阵分解DMFDMFDMF和深度神经网络DNNDNNDNN。该模型能够更加智能地解决稀疏矩阵问题,更准确地预测BODBODBOD值。为了测试其有效性,我们依据32323个水样对纽约市港口水域进行了案例研究。结果表明,该方法比传统矩阵补全方法降低了11.54%-17.23%的RMSERMSERMSE,比传统的机器学习算法降低了19.20%-25.16%的RMSERMSERMSE。\n注:\nRMSE(Root mean squared error): 剩余标准差SES_ESE,也称均方差,统计学概念,在线性回归分析中,真实值和估计值之间的差称为残差(或者剩余量),所有预测值的残差平方和(或者剩余平方和),剩余标准差就是剩余平方和的开平方。用来表示估计值的精度。)\n 1.介绍\n 1.1.背景\n\t港口水由自然或人为屏障形成,是最为重要的地表水组成之一,对于纽约、旧金山、香港和上海等城市,港口流入了大部分城市雨水和城市污水,然而在某些情况下,这些污水很少能得到处理,以至于在城市港口造成各种水污染。细菌、过量的营养物质、有毒化学物质和其他污染物会影响周边人群的健康,也危机海洋生物。为了减少其对环境的严重影响,城市管理者投入了大量精力控制和改善港口水质。例如,纽约市在过去十年投入了超过120亿美元升级城市下水道系统,减少港口水的环境影响。旧金山从2008年起已向旧金山港口水质改善基金会拨款超过5000多万美元。香港推出了不同类型的总体规划,包括保护港口用水的污染者罚款政策。\n为了更好地控制和管理港口水质,对关键的质量指标进行追踪是很重要的,例如温度、溶解氧、pH、营养素和金属。这些水质指标中许多可以通过现代传感器进行实时监测,然而,有一个关键指标不仅体现了水质的关键信息,而且需要花费大量时间和精力进行检测:生化需氧量(BOD)指标。这个指标的定义是:需氧生物在特定温度(通常为20℃)下,在特定时间段内(通常为5天,标记为BOD5BOD_5BOD5)内分解水中有机物所需要的溶解氧量。如果BOD含量过高,水中溶解氧可能被耗尽,导致需氧生物难以生存。因此,监测水质时,测量BOD5BOD_5BOD5含量是十分重要的。\n\t尽管BOD5BOD_5BOD5是一项十分有价值的指标,但是其测量时间会造成很大的限制。根据标准,BOD样品的保存时间为采集后的48小时,测试本身需要5天的潜伏期,这种时间约束导致了管理和研究的各种弊端,该问题引起了人们的广泛关注。\n 1.2 文献综述\n","categories":["论文"],"tags":[]},{"title":"贪心算法","url":"/algorithm-greedy/","content":"区间选点\n将每个区间按照右端点从小到大排序,设ed为上一个点的右端点\n遍历区间的左端点,如果左端点小于等于ed,则遍历下一个区间\n否则ed为当前区间的右端点,答案加一\n最大不相交区间数\n将每个区间按照右端点从小到大排序,设ed为上一个点的右端点\n遍历区间的左端点,如果左端点小于ed,则遍历下一个区间\n否则ed为当前区间的右端点,答案加一\n区间分组\n将区间按照左端点进行从小到大排序\n遍历每个区间,判断能否将其放在某个现有的组中(判断l[i]<maxrl[i]<max_rl[i]<maxr是否成立)\n如果不存在这样的组,则开一个新祖,将其放进去\n如果存在这样的组,将其放进去,更新当前组的maxrmax_rmaxr\n区间覆盖\n先将所有区间按左端点排序,枚举每个区间,第一次选择能覆盖s点的区间中右端点最大的区间,将s更新为右端点的最大值。\n合并果子\n选择值最小的两堆果子进行合并即可,可以用优先队列求解(记得把两者之和加回队列)\n排队打水\n\n有 n 个人在一个水龙头前排队接水,假如每个人接水的时间为TiT_iTi,请编程找出这 n 个人排队的一种顺序,使得 n 个人的平均等待时间最小。\n\nT=t1∗(n−1)+t2∗(n−2)+...+tn−2∗2+tn−1∗1T=t_1*(n-1)+t_2*(n-2)+...+t_{n-2}*2+t_{n-1}*1T=t1∗(n−1)+t2∗(n−2)+...+tn−2∗2+tn−1∗1\n所以尽可能将小的t放在前面与较大的系数相乘,即按照从小到大排队,总时间最小。\n货仓地址\n奶牛耍杂技\nAcwing中修改题干为:求最大压扁值的可能最小值,不影响求解思路及结果\n按照wi+siw_i+s_iwi+si排序,依次遍历求出压扁值的最大值,输出答案即可。\n","categories":["算法"],"tags":[]},{"title":"dp问题合集","url":"/algorithm-dp/","content":" 动态规划\n\n状态表示f[i,j]f[i,j]f[i,j]\n\n集合\n属性(Max,Min,Cnt)\n\n\n状态计算\n\n集合的划分\n\n\n\n 线性DP\n数字三角形\nO(n2)O(n^2)O(n2)\nf[i,j]f[i,j]f[i,j]表示到坐标为[i,j][i,j][i,j]的路径的和最大值\nf[1][1]=a[1][1]f[1][1] = a[1][1]f[1][1]=a[1][1]\nf[i][j]=max(f[i−1][j−1]+a[i][j],f[i−1][j]+a[i][j])f[i][j] = max(f[i-1][j-1]+a[i][j], f[i-1][j]+a[i][j])f[i][j]=max(f[i−1][j−1]+a[i][j],f[i−1][j]+a[i][j])\n最长上升子序列\n朴素:O(n2)O(n^2)O(n2)\nf[i]f[i]f[i]表示以第iii个数结尾的序列中上升子序列长度的最大值\n遍历aia_iai所有可能的前一个数aja_jaj(aj<aia_j<a_iaj<ai且0≤j≤i−10 \\le j \\le i-10≤j≤i−1)\nf[i]=max(f[j]+1),j∈[0,i−1]f[i] = max(f[j]+1),j \\in [0,i-1]f[i]=max(f[j]+1),j∈[0,i−1]\n如果要保存最长序列:g[i]g[i]g[i]保存从哪一步jjj转移过来\n优化:O(nlogn)O(nlogn)O(nlogn)\n最长公共子序列\n朴素:O(n2)O(n^2)O(n2)\nf[i][j]f[i][j]f[i][j]表示所有在第一个序列的前i个字母中出现,且在第二个序列的前j个字母中出现的子序列的最大值\n集合划分依据:是否选择a[i],b[j]a[i],b[j]a[i],b[j]\n分为四个集合:选择a[i],b[j]a[i],b[j]a[i],b[j] ; 选择a[i]a[i]a[i] 不选择b[j]b[j]b[j] ; 不选择a[j]a[j]a[j]选择b[j]b[j]b[j] ; 都不选择a[i],b[j]a[i],b[j]a[i],b[j]\n分别表示为 f[i−1][j−1],f[i,j−1],f[i−1][j],f[i−1][j−1]+1f[i-1][j-1] , f[i,j-1] , f[i-1][j] , f[i-1][j-1]+1f[i−1][j−1],f[i,j−1],f[i−1][j],f[i−1][j−1]+1\n其中第二三种情况包含上面对应的集合(由于是求Max,所以有重复不影响结果)\n且第二三种集合也包含第一个集合,所以只要对后三种集合求最大值即可\nf[i,j]=max(f[i−1,j],f[i,j−1])f[i,j] = max(f[i-1,j],f[i,j-1])f[i,j]=max(f[i−1,j],f[i,j−1])\n当a[i]==b[j]a[i]==b[j]a[i]==b[j]时,f[i,j]=max(f[i,j],f[i−1,j−1]+1)f[i,j] = max(f[i,j],f[i-1,j-1]+1)f[i,j]=max(f[i,j],f[i−1,j−1]+1)\n优化:O(nlogn)O(nlogn)O(nlogn)\n 区间DP\n石子合并\nf[i,j]f[i,j]f[i,j]表示将第iii堆石子到第jjj堆石子合并成一堆石子的方式的代价最小值/最大值\nO(n3)O(n^3)O(n3)\nfor(int len=2;len<=n;len++){\n for(int i=1;i+len-1<=n;i++){\n int l = i,r = i+len-1;\n f_max[l][r] = -1e8,f_min[l][r] = 1e8;\n for(int k=l;k<r;k++){\n f_max[l][r] = max(f_max[l][r],f_max[l][k]+f_max[k+1][r]+s[r]-s[l-1]);\n f_min[l][r] = min(f_min[l][r],f_min[l][k]+f_min[k+1][r]+s[r]-s[l-1]);\n }\n }\n}\n 计数类DP\n 数位统计DP\n计数问题\n设n=abcdefgn=abcdefgn=abcdefg,枚举第iii位是 x∈[0,9]x \\in [0,9]x∈[0,9]\n举例x=1,i=4x=1,i=4x=1,i=4的情况:\n设数字为xxx1yyyxxx1yyyxxx1yyy\n\n当abc>xxx,xxx∈[000,abc−1],y∈[000,999]abc>xxx,xxx \\in [000,abc-1], y \\in [000,999]abc>xxx,xxx∈[000,abc−1],y∈[000,999],则共有abc∗1000abc * 1000abc∗1000个\n当abc<xxxabc<xxxabc<xxx,则共有0个\n当abc=xxxabc=xxxabc=xxx\n\n当d<1d<1d<1,无解\n当d=1d=1d=1,yyy∈[000,efg]yyy \\in [000,efg]yyy∈[000,efg],则有efg+1efg+1efg+1种\n当d>1d>1d>1,yyy∈[000,999]yyy \\in [000,999]yyy∈[000,999],有1000种\n\n\n\n当x=0时,注意前导0,即对于第一种情况,xxx∈[001,abc−1]xxx \\in [001,abc-1]xxx∈[001,abc−1],即有(abc−1)∗1000(abc-1)*1000(abc−1)∗1000情况\n圆形数字\n 状态压缩DP\n蒙德里安的梦想\nf[i][j]f[i][j]f[i][j]表示第i列,上一列横着摆的数量j,其中j是一个二进制数。\n最短Hamilton路径\nf[i][j]f[i][j]f[i][j]表示从0号点走到j号点,走过的所有点是i的所有路径(二进制数i表示某个点是否已经走过了)的最小路径长度\n 树形DP\n没有上司的舞会\nf[u][0]f[u][0]f[u][0]表示所有以u为根的子树中选择,并且不选u这个点的方案的最大值\nf[u][1]f[u][1]f[u][1]表示所有以u为根的子树中选择,并且选u这个点的方案的最大值\n设点u的子节点s1,s2,s3....sis_1,s_2,s_3....s_is1,s2,s3....si\nf[u][0]=∑1imax(f[si][0],f[si][1])f[u][0] = \\sum_{1}^{i}max(f[s_i][0],f[s_i][1])f[u][0]=∑1imax(f[si][0],f[si][1])\nf[u][1]=∑1if[si][0]f[u][1] = \\sum_{1}^{i}f[s_i][0]f[u][1]=∑1if[si][0]\n找出根节点,递归求最大值即可\n 记忆化搜索\n滑雪\n用s[i][j]s[i][j]s[i][j]表示从(i,j)点出发能走的最长距离。\n每次搜索一次记忆一次即可。\n举例\n3 3 \n1 1 3\n2 3 4\n1 1 1\n先去找(1,1)的最长距离,很明显为1\n接着找(1,2)的最长距离,很明显为1\n接着找(1,3)的最长距离,为2((1,3)->(1,2))\n然后找(2,1)的最长距离,为2((2,1)->(1,1))\n然后是(2,2)的最长距离,如果没有记忆化,那么搜索过程为:(2,2)->(2,1)->(1,1)\n但是(2,1)之前已经搜过了,再去搜就是浪费时间,之前搜索已经知道(2,1)的值为2,那么搜索过程就是缩短为:(2,2)->(2,1),即为3\n 杂题\nkkksc03考前临时抱佛脚\n","categories":["算法"],"tags":[]},{"title":"背包问题算法","url":"/algorithm-knapsack-problem/","content":" 动态规划\n\n状态表示f[i,j]f[i,j]f[i,j]\n\n集合\n属性(Max,Min,Cnt)\n\n\n状态计算\n\n集合的划分\n\n\n\n 背包问题\n给定nnn个物品和容量vvv的背包,每个物品都有体积viv_ivi和价值wiw_iwi,求当∑i=1nvi≤v\\sum_{i=1}^{n} v_i \\le v∑i=1nvi≤v时最大的www是多少\n 01背包问题\n每个物品只能用0/1次\nf[i,j]=max(f[i−1,j],f[i−1,j−vi]+wi)f[i,j] = max(f[i-1,j],f[i-1,j-v_i]+w_i)f[i,j]=max(f[i−1,j],f[i−1,j−vi]+wi)\n01背包问题\n采药\n 完全背包问题\n物品可以无限次使用\nf[i,j]=Max(f[i−1,j−vi×k]+w[i]×k)f[i,j] = Max(f[i-1,j-v_i \\times k]+w[i] \\times k)f[i,j]=Max(f[i−1,j−vi×k]+w[i]×k)\nk⊆[0,jvi]k \\subseteq [0,\\frac{j}{v_i}]k⊆[0,vij]\n即f[i,j]=Max(f[i−1,j],f[i−1,j−vi]+wi,f[i−1,j−2vi]+2wi,....,f[i−1][j−kvi]+kwi)f[i,j] = Max(f[i-1,j],f[i-1,j-v_i]+w_i,f[i-1,j-2v_i]+2w_i,....,f[i-1][j-kv_i]+kw_i)f[i,j]=Max(f[i−1,j],f[i−1,j−vi]+wi,f[i−1,j−2vi]+2wi,....,f[i−1][j−kvi]+kwi)\nf[i,j−vi]=Max(f[i−1][j−vi],f[i−1][j−2vi]+wi,...,f[i−1][j−kvi]+(k−1)wi)f[i,j-v_i] = Max(f[i-1][j-v_i],f[i-1][j-2v_i]+w_i,...,f[i-1][j-kv_i]+(k-1)w_i)f[i,j−vi]=Max(f[i−1][j−vi],f[i−1][j−2vi]+wi,...,f[i−1][j−kvi]+(k−1)wi)\nf[i][j]f[i][j]f[i][j]的后kkk项等于f[i][j−vi]+wif[i][j-v_i]+w_if[i][j−vi]+wi\n得\nf[i,j]=Max(f[i−1,j],f[i,j−vi]+wi)f[i,j] = Max(f[i-1,j],f[i,j-v_i]+w_i)f[i,j]=Max(f[i−1,j],f[i,j−vi]+wi)\n完全背包问题\n 多重背包物品\n每个物品的个数不一致\n朴素做法\nf[i,j]=Max(f[i−1,j],f[i−1,j−vi]+wi,f[i−1,j−2vi]+2wi,....,f[i−1][j−kvi]+kwi)f[i,j] = Max(f[i-1,j],f[i-1,j-v_i]+w_i,f[i-1,j-2v_i]+2w_i,....,f[i-1][j-kv_i]+kw_i)f[i,j]=Max(f[i−1,j],f[i−1,j−vi]+wi,f[i−1,j−2vi]+2wi,....,f[i−1][j−kvi]+kwi)\nk⊆[0,si]k \\subseteq [0,s_i]k⊆[0,si]\n三重循环即可\n多重背包问题1\n优化:二进制优化\nfor(int i=1;i<=n;i++){\n int a,b,s;\n cin>>a>>b>>s;\n //v w s;\n int k = 1;\n while(k<=s){\n cnt++;\n v[cnt] = a*k;\n w[cnt] = b*k;\n s-=k;\n k*=2;\n }\n if(s>0){\n cnt++;\n v[cnt] = a*s;\n w[cnt] = b*s;\n }\n}\n对物品进行二进制分组,组数为cntcntcnt,转化为01背包问题求解\nn = cnt;\nfor(int i=1;i<=n;i++){\n for(int j=0;j<=m;j++){\n f[i][j] = f[i-1][j];\n if(j>=v[i]) f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);\n }\n}\ncout<<f[n][m]<<endl;\n多重背包问题2\n 分组背包问题\n有NNN组,每一组只能选其中一种物品\nf[i][j]=Max(f[i−1,j],f[i−1,j−vi,k]+wi,k)f[i][j] = Max(f[i-1,j],f[i-1,j-v_{i,k}]+w_{i,k})f[i][j]=Max(f[i−1,j],f[i−1,j−vi,k]+wi,k)\n分组背包问题\n","categories":["算法"],"tags":[]},{"title":"算法数论","url":"/algorithm-math/","content":" 质数\n对于大于一的整数,如果只包含一和本身这两个约数,它就是质数(也叫素数)\n 试除法\nO(n)O(\\sqrt n)O(n)\nbool is_prime(int x)\n{\n if (x < 2) return false;\n for (int i = 2; i <= x / i; i ++ )\n if (x % i == 0)\n return false;\n return true;\n}\n 试除法分解质因数\nvoid divide(int x)\n{\n for (int i = 2; i <= x / i; i ++ )\n if (x % i == 0)\n {\n int s = 0;\n while (x % i == 0) x /= i, s ++ ;\n cout << i << ' ' << s << endl;\n }\n if (x > 1) cout << x << ' ' << 1 << endl;\n cout << endl;\n}\n 朴素筛法求素数\n枚举每一个数,如果它没有被筛,则加入质数集合,并且把它的所有倍数都筛掉\nO(nloglogn)O(nloglogn)O(nloglogn)\nint primes[N], cnt; // primes[]存储所有素数\nbool st[N]; // st[x]存储x是否被筛掉\n\nvoid get_primes(int n)\n{\n for (int i = 2; i <= n; i ++ )\n {\n if (st[i]) continue;\n primes[cnt ++ ] = i;\n for (int j = i + i; j <= n; j += i)\n st[j] = true;\n }\n}\n 线性筛法求素数\nint primes[N], cnt; // primes[]存储所有素数\nbool st[N]; // st[x]存储x是否被筛掉\n\nvoid get_primes(int n)\n{\n for (int i = 2; i <= n; i ++ )\n {\n if (!st[i]) primes[cnt ++ ] = i;\n for (int j = 0; primes[j] <= n / i; j ++ )\n {\n st[primes[j] * i] = true;\n if (i % primes[j] == 0) break;\n }\n }\n}\n 约数\n 试除法求所有约数\nvector<int> get_divisors(int x)\n{\n vector<int> res;\n for (int i = 1; i <= x / i; i ++ )\n if (x % i == 0)\n {\n res.push_back(i);\n if (i != x / i) res.push_back(x / i);\n }\n sort(res.begin(), res.end());\n return res;\n}\n 约数个数和约数之和\n如果 N = p1^c1 * p2^c2 * … *pk^ck\n约数个数: (c1 + 1) * (c2 + 1) * … * (ck + 1)\n约数之和: (p1^0 + p1^1 + … + p1^c1) * … * (pk^0 + pk^1 + … + pk^ck)\n 欧几里得算法求最大公约数\nint gcd(int a, int b)\n{\n return b ? gcd(b, a % b) : a;\n}\n 欧拉函数\nϕ(n)\\phi(n)ϕ(n):1-n中与n互质的数的个数\nint phi(int x)\n{\n int res = x;\n for (int i = 2; i <= x / i; i ++ )\n if (x % i == 0)\n {\n res = res / i * (i - 1);\n while (x % i == 0) x /= i;\n }\n if (x > 1) res = res / x * (x - 1);\n\n return res;\n}\n 筛法求欧拉函数\nint primes[N], cnt; // primes[]存储所有素数\nint euler[N]; // 存储每个数的欧拉函数\nbool st[N]; // st[x]存储x是否被筛掉\n\n\nvoid get_eulers(int n)\n{\n euler[1] = 1;\n for (int i = 2; i <= n; i ++ )\n {\n if (!st[i])\n {\n primes[cnt ++ ] = i;\n euler[i] = i - 1;\n }\n for (int j = 0; primes[j] <= n / i; j ++ )\n {\n int t = primes[j] * i;\n st[t] = true;\n if (i % primes[j] == 0)\n {\n euler[t] = euler[i] * primes[j];\n break;\n }\n euler[t] = euler[i] * (primes[j] - 1);\n }\n }\n}\n 快速幂\n在O(logk)O(logk)O(logk)时间内求出求出akmodpa^k mod pakmodp\nint qmi(int m, int k, int p)\n{\n int res = 1 % p, t = m;\n while (k)\n {\n if (k&1) res = res * t % p;\n t = t * t % p;\n k >>= 1;\n }\n return res;\n}\n 扩展欧几里得算法\n// 求x, y,使得ax + by = gcd(a, b)\nint exgcd(int a, int b, int &x, int &y)\n{\n if (!b)\n {\n x = 1; y = 0;\n return a;\n }\n int d = exgcd(b, a % b, y, x);\n y -= (a/b) * x;\n return d;\n}\n 中国剩余定理\n给定一些两两互质的数,求解线性同余方程组\nToBeContinueTo Be ContinueToBeContinue\n 高斯消元\n在O(n3)O(n^3)O(n3)内求解线性方程组\n// a[N][N]是增广矩阵\nint gauss()\n{\n int c, r;\n for (c = 0, r = 0; c < n; c ++ )\n {\n int t = r;\n for (int i = r; i < n; i ++ ) // 找到绝对值最大的行\n if (fabs(a[i][c]) > fabs(a[t][c]))\n t = i;\n\n if (fabs(a[t][c]) < eps) continue;\n\n for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]); // 将绝对值最大的行换到最顶端\n for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c]; // 将当前行的首位变成1\n for (int i = r + 1; i < n; i ++ ) // 用当前行将下面所有的列消成0\n if (fabs(a[i][c]) > eps)\n for (int j = n; j >= c; j -- )\n a[i][j] -= a[r][j] * a[i][c];\n\n r ++ ;\n }\n\n if (r < n)\n {\n for (int i = r; i < n; i ++ )\n if (fabs(a[i][n]) > eps)\n return 2; // 无解\n return 1; // 有无穷多组解\n }\n`\n for (int i = n - 1; i >= 0; i -- )\n for (int j = i + 1; j < n; j ++ )\n a[i][n] -= a[i][j] * a[j][n];\n\n return 0; // 有唯一解\n}\n 组合数\n 递推法求组合数\nCab=Ca−1b+Ca−1b−1C_{a}^{b} = C_{a-1}^{b} + C_{a-1}^{b-1}Cab=Ca−1b+Ca−1b−1\n// c[a][b] 表示从a个苹果中选b个的方案数\nfor (int i = 0; i < N; i ++ )\n for (int j = 0; j <= i; j ++ )\n if (!j) c[i][j] = 1;\n else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;\n","categories":["算法"],"tags":[]},{"title":"最小生成树与二分图算法","url":"/algorithm-minimum-spanning-tree/","content":" 最小生成树\n Prim算法\n处理稠密图\n朴素Prim算法 O(n2)O(n^2)O(n2)\n类似dijkstra,找出距离集合最短的点,加入集合,更新其他点到集合的距离\nint n; // n表示点数\nint g[N][N]; // 邻接矩阵,存储所有边\nint dist[N]; // 存储其他点到当前最小生成树的距离\nbool st[N]; // 存储每个点是否已经在生成树中\n\n\n// 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和\nint prim()\n{\n memset(dist, 0x3f, sizeof dist);\n\n int res = 0;\n for (int i = 0; i < n; i ++ )\n {\n int t = -1;\n for (int j = 1; j <= n; j ++ )\n if (!st[j] && (t == -1 || dist[t] > dist[j]))\n t = j;\n\n if (i && dist[t] == INF) return INF;\n\n if (i) res += dist[t];\n st[t] = true;\n\n for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);\n }\n\n return res;\n}\n堆优化Prim O(mlogn)O(mlogn)O(mlogn) (不常用)\n Kruskal算法\nO(mlogm)O(mlogm)O(mlogm) 处理稀疏图\n\n将所有边按照权重排序\n枚举每条边a->b,权重c (如果ab不联通,则将边ab加入集合中)\n\nint n, m; // n是点数,m是边数\nint p[N]; // 并查集的父节点数组\n\nstruct Edge // 存储边\n{\n int a, b, w;\n\n bool operator< (const Edge &W)const\n {\n return w < W.w;\n }\n}edges[M];\n\nint find(int x) // 并查集核心操作\n{\n if (p[x] != x) p[x] = find(p[x]);\n return p[x];\n}\n\nint kruskal()\n{\n sort(edges, edges + m);\n\n for (int i = 1; i <= n; i ++ ) p[i] = i; // 初始化并查集\n\n int res = 0, cnt = 0;\n for (int i = 0; i < m; i ++ )\n {\n int a = edges[i].a, b = edges[i].b, w = edges[i].w;\n\n a = find(a), b = find(b);\n if (a != b) // 如果两个连通块不连通,则将这两个连通块合并\n {\n p[a] = b;\n res += w;\n cnt ++ ;\n }\n }\n\n if (cnt < n - 1) return INF;\n return res;\n}\n【模版】最小生成树\n买礼物\n 二分图\n定义:设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图\n一个图是二分图当且仅当图中不含有奇数环\n 染色法\nO(n+m)O(n+m)O(n+m) 判断是否为二分图\nint n; // n表示点数\nint h[N], e[M], ne[M], idx; // 邻接表存储图\nint color[N]; // 表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色\n\n// 参数:u表示当前节点,c表示当前点的颜色\nbool dfs(int u, int c)\n{\n color[u] = c;\n for (int i = h[u]; i != -1; i = ne[i])\n {\n int j = e[i];\n if (color[j] == -1)\n {\n if (!dfs(j, !c)) return false;\n }\n else if (color[j] == c) return false;\n }\n\n return true;\n}\n\nbool check()\n{\n memset(color, -1, sizeof color);\n bool flag = true;\n for (int i = 1; i <= n; i ++ )\n if (color[i] == -1)\n if (!dfs(i, 0))\n {\n flag = false;\n break;\n }\n return flag;\n}\n 匈牙利算法\nO(mn)O(mn)O(mn),实际运行时间远小于O(mn)O(mn)O(mn)\n实现二分图的最大匹配\nint n1, n2; // n1表示第一个集合中的点数,n2表示第二个集合中的点数\nint h[N], e[M], ne[M], idx; // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边\nint match[N]; // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个\nbool st[N]; // 表示第二个集合中的每个点是否已经被遍历过\n\nbool find(int x)\n{\n for (int i = h[x]; i != -1; i = ne[i])\n {\n int j = e[i];\n if (!st[j])\n {\n st[j] = true;\n if (match[j] == 0 || find(match[j]))\n {\n match[j] = x;\n return true;\n }\n }\n }\n\n return false;\n}\n\n// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点\nint res = 0;\nfor (int i = 1; i <= n1; i ++ )\n{\n memset(st, false, sizeof st);\n if (find(i)) res ++ ;\n}\n","categories":["算法"],"tags":[]},{"title":"最短路算法","url":"/algorithm-shortest-path/","content":" 最短路\nnnn为点数,mmm为边数\n若mmm与n2n^2n2同一级别为稠密图,与nnn同一级别为稀疏图\n稠密图使用邻接矩阵储存,稀疏图用邻接表储存\n\n单源最短路\n\n所有边权都是正数\n\n朴素dijkstradijkstradijkstra算法 O(n2+m)O(n^2+m)O(n2+m)\n堆优化版dijkstradijkstradijkstra算法 O(mlogn)O(mlogn)O(mlogn)\n\n\n存在负权边\n\nBellman−FordBellman-FordBellman−Ford算法 O(nm)O(nm)O(nm)\nSPFASPFASPFA算法 一般O(m)O(m)O(m),最坏O(nm)O(nm)O(nm)\n\n\n\n\n多源汇最短路\n\nfloydfloydfloyd算法 O(n3)O(n^3)O(n3)\n\n\n\n 朴素dijkstra算法\n\n\n初始化距离,$dist[1]=0,dist[i]=+\\infty $,st数组:当前已经确定最短路径的点\n\n\n循环每一个点,找到不在st中的最短距离点t,t加入到st中,用t更新其他点的距离\n\n\nint g[N][N]; // 存储每条边\nint dist[N]; // 存储1号点到每个点的最短距离\nbool st[N]; // 存储每个点的最短路是否已经确定\n\n// 求1号点到n号点的最短路,如果不存在则返回-1\nint dijkstra()\n{\n memset(dist, 0x3f, sizeof dist);\n dist[1] = 0;\n\n for (int i = 0; i < n - 1; i ++ )\n {\n int t = -1; // 在还未确定最短路的点中,寻找距离最小的点\n for (int j = 1; j <= n; j ++ )\n if (!st[j] && (t == -1 || dist[t] > dist[j]))\n t = j;\n\n // 用t更新其他点的距离\n for (int j = 1; j <= n; j ++ )\n dist[j] = min(dist[j], dist[t] + g[t][j]);\n\n st[t] = true;\n }\n\n if (dist[n] == 0x3f3f3f3f) return -1;\n return dist[n];\n}\n一般边数m比较多,所以使用邻接矩阵g[a][b]存储\n 堆优化版dijkstra算法\n将寻找距离最小的点的时间复杂度降低\n堆可以使用手写堆或优先队列\ntypedef pair<int, int> PII;\n\nint n; // 点的数量\nint h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边\nint dist[N]; // 存储所有点到1号点的距离\nbool st[N]; // 存储每个点的最短距离是否已确定\nvoid add(int a, int b, int c){\n e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;\n}\n// 求1号点到n号点的最短距离,如果不存在,则返回-1\nint dijkstra()\n{\n memset(dist, 0x3f, sizeof dist);\n dist[1] = 0;\n priority_queue<PII, vector<PII>, greater<PII>> heap;\n heap.push({0, 1}); // first存储距离,second存储节点编号\n\n while (heap.size())\n {\n auto t = heap.top();\n heap.pop();\n\n int ver = t.second, distance = t.first;\n\n if (st[ver]) continue;\n st[ver] = true;\n\n for (int i = h[ver]; i != -1; i = ne[i])\n {\n int j = e[i];\n if (dist[j] > distance + w[i])\n {\n dist[j] = distance + w[i];\n heap.push({dist[j], j});\n }\n }\n }\n\n if (dist[n] == 0x3f3f3f3f) return -1;\n return dist[n];\n}\n Bellman-Ford算法\n迭代n次,每次遍历所有边,对dist[b]进行更新\nint n, m; // n表示点数,m表示边数\nint dist[N]; // dist[x]存储1到x的最短路距离\n\nstruct Edge // 边,a表示出点,b表示入点,w表示边的权重\n{\n int a, b, w;\n}edges[M];\n\n// 求1到n的最短路距离,如果无法从1走到n,则返回-1。\nint bellman_ford()\n{\n memset(dist, 0x3f, sizeof dist);\n dist[1] = 0;\n\n // 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。\n for (int i = 0; i < n; i ++ )\n {\n for (int j = 0; j < m; j ++ )\n {\n int a = edges[j].a, b = edges[j].b, w = edges[j].w;\n if (dist[b] > dist[a] + w)\n dist[b] = dist[a] + w;\n }\n }\n\n if (dist[n] > 0x3f3f3f3f / 2) return -1;\n return dist[n];\n}\n//遍历完后满足三角不等式\ndist[b] <= dist[a] + w\n可以用于找负环,时间复杂度比较高\n SPFA算法\n队列优化的Bellman-Ford算法\nint n; // 总点数\nint h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边\nint dist[N]; // 存储每个点到1号点的最短距离\nbool st[N]; // 存储每个点是否在队列中\n\n// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1\nint spfa()\n{\n memset(dist, 0x3f, sizeof dist);\n dist[1] = 0;\n\n queue<int> q;\n q.push(1);\n st[1] = true;\n\n while (q.size())\n {\n auto t = q.front();\n q.pop();\n\n st[t] = false;\n\n for (int i = h[t]; i != -1; i = ne[i])\n {\n int j = e[i];\n if (dist[j] > dist[t] + w[i])\n {\n dist[j] = dist[t] + w[i];\n if (!st[j]) // 如果队列中已存在j,则不需要将j重复插入\n {\n q.push(j);\n st[j] = true;\n }\n }\n }\n }\n\n if (dist[n] == 0x3f3f3f3f) return -1;\n return dist[n];\n}\n推荐使用,只要不被卡或者存在负环\n判断负环\n在进行更新dist[j] = dist[t] + w时,同时维护cnt[x] (1号点到x号点经过的边数)\ncnt[x] = cnt[t]+1;\n若某个cnt[x] 大于等于n,则说明存在负环\nint n; // 总点数\nint h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边\nint dist[N], cnt[N]; // dist[x]存储1号点到x的最短距离,cnt[x]存储1到x的最短路中经过的点数\nbool st[N]; // 存储每个点是否在队列中\n\n// 如果存在负环,则返回true,否则返回false。\nbool spfa()\n{\n // 不需要初始化dist数组\n // 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。\n\n queue<int> q;\n for (int i = 1; i <= n; i ++ )\n {\n q.push(i);\n st[i] = true;\n }\n\n while (q.size())\n {\n auto t = q.front();\n q.pop();\n\n st[t] = false;\n\n for (int i = h[t]; i != -1; i = ne[i])\n {\n int j = e[i];\n if (dist[j] > dist[t] + w[i])\n {\n dist[j] = dist[t] + w[i];\n cnt[j] = cnt[t] + 1;\n if (cnt[j] >= n) return true; // 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环\n if (!st[j])\n {\n q.push(j);\n st[j] = true;\n }\n }\n }\n }\n\n return false;\n}\n【模版】单源最短路径(弱化版)\n【模版】单源最短路径(标准版)\n Floyd算法\n基于动态规划\n初始化:\n for (int i = 1; i <= n; i ++ )\n for (int j = 1; j <= n; j ++ )\n if (i == j) d[i][j] = 0;\n else d[i][j] = INF;\n\n// 算法结束后,d[a][b]表示a到b的最短距离\nvoid floyd()\n{\n for (int k = 1; k <= n; k ++ )\n for (int i = 1; i <= n; i ++ )\n for (int j = 1; j <= n; j ++ )\n d[i][j] = min(d[i][j], d[i][k] + d[k][j]);\n}\n【模板】Floyd 算法\n","categories":["算法"],"tags":[]},{"title":"递归与搜索","url":"/algorithm-search/","content":" DFS与BFS\n\n深度优先搜索(DFS)\n\n用StackStackStack递归,空间O(h)O(h)O(h),不具有最短性\n题目:全排列、八皇后\n\n宽度优先搜索(BFS)\n\n用QueueQueueQueue,空间O(2h)O(2^h)O(2h),“最短路”\n题目:迷宫\n回溯、剪枝\n烤鸡\n猫猫和企鹅\n在矩阵中4个方向遍历\nint dx[] = {1,0,-1,0},y = {0,1,0,-1};\n防止走相反的方向导致搜索回溯\nif(i ^ 2 == d) continue;\n8个方向遍历\nint dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};\nint dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};\n防止走相反的方向导致搜索回溯\nif(i ^ 4 == d) continue;\n 树和图的存储\n树是特殊的无环连通图\n有向图a→ba \\to ba→b\n\n\n邻接矩阵 g[a][b]g[a][b]g[a][b]\n\n\n邻接表,用链表储存点iii可以到达的点\n\n\n// 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点\nint h[N], e[N], ne[N], idx;\n\n// 添加一条边a->b\nvoid add(int a, int b)\n{\n e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;\n}\n\n// 初始化\nidx = 0;\nmemset(h, -1, sizeof h);\n 树和图的遍历\n时间复杂度O(n+m)O(n+m)O(n+m),n表示点数,m表示边数\n\n深度优先遍历\n\nint dfs(int u)\n{\n st[u] = true; // st[u] 表示点u已经被遍历过\n\n for (int i = h[u]; i != -1; i = ne[i])\n {\n int j = e[i];\n if (!st[j]) dfs(j);\n }\n}\n\n宽度优先遍历\n\nqueue<int> q;\nst[1] = true; // 表示1号点已经被遍历过\nq.push(1);\n\nwhile (q.size())\n{\n int t = q.front();\n q.pop();\n\n for (int i = h[t]; i != -1; i = ne[i])\n {\n int j = e[i];\n if (!st[j])\n {\n st[j] = true; // 表示点j已经被遍历过\n q.push(j);\n }\n }\n}\n 拓扑排序\n时间复杂度O(n+m)O(n+m)O(n+m),n表示点数,m表示边数\n有向无环图一定可以拓扑排序,序列可能不唯一\n入度、出度:有多少条边指向自己/从自己这里指出去\n\n\n将入度为0的点入队\n\n\n宽搜,枚举队头的所有出边t→jt \\to jt→j,删掉t→jt \\to jt→j,ttt的出度减一\n\n\nbool topsort()\n{\n int hh = 0, tt = -1;\n\n // d[i] 存储点i的入度\n for (int i = 1; i <= n; i ++ )\n if (!d[i])\n q[ ++ tt] = i;\n\n while (hh <= tt)\n {\n int t = q[hh ++ ];\n\n for (int i = h[t]; i != -1; i = ne[i])\n {\n int j = e[i];\n d[j]--;\n if (d[j] == 0)\n q[ ++ tt] = j;\n }\n }\n\n // 如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。\n return tt == n - 1;\n}\n一个有向无环图至少有一个入度为0的点\n","categories":["算法"],"tags":[]},{"title":"Vue学习笔记","url":"/vue/","content":" 代码风格\nVue2:选项式API\nVue3:组合式API\n 准备工作\n首先配置Node.jsNode.jsNode.js环境,然后\nnpm init vue@latest\n初始化名称namenamename(不要用大写字母)\n一路勾选NONONO\nIDE使用VSCodeVSCodeVSCode+VolarVolarVolar插件\n进入namenamename文件夹cd name\n执行npm install\nnpm run dev即可运行项目\n 文件目录\nnode_modules 依赖文件夹\npublic 资源文件夹\nsrc 源码文件夹\nindex.html 入口HTML文件\npackage.json 信息描述文件\nvite.config.js Vue配置文件\n 模版语法\n\n文本插值\n\n\t{{msg}},其中可以使用JavaScrpitJavaScrpitJavaScrpit表达式\n\t{{number +1}} 、{{ok ? 'Yes' : 'No'}}、{{msg.split('')}}\n\n原始html\n\n\t使用v-html\n 属性绑定\nv-bind\n文本的绑定使用{{}}绑定,而属性(id、class等)的绑定使用v-bind\n<div v-bind:id="ID" v-bind:class="MyClass">\n {{msg}}\n</div>\n其中ID、MyClass、msg都是data变量\n简写::属性\n例如\n<div :id="ID" :class="MyClass">\n {{msg}}\n</div>\n将属性存进同一个对象可以实现一次性绑定多个值\n 条件渲染\nv-if v-else v-else-if v-show\nv-if:如果初次渲染时为false,就不会被渲染,有较高的切换开销\nv-show:无论初始值如何都会被渲染,后续根据CSS的display属性切换,有较高的初始渲染开销\n如果需要频繁切换,用v-show\n 列表渲染\nv-for指令基于item in items\n如果要获取下标,使用(item,index) in items\n对于对象数组,则是(value,key,index)\n 通过key管理状态\nVue默认按照“就地更新”,重新渲染消耗性能\n不推荐Key绑定index,而是绑定id等唯一索引属性\n 事件处理\n使用v-on或者@监听DOM事件\n\n内联事件处理器:内联JS语句\n方法事件处理器:指向方法\n\n 事件参数\n在方法中传递参数到事件中\nadd(count){\n\tthis.num += count;\n}\n传递$event获取JS的event对象\n 事件修饰符\n简化代码\n文档\n<!-- 阻止单击事件继续传播 -->\n<a v-on:click.stop="doThis"></a>\n\n<!-- 提交事件不再重载页面 -->\n<form v-on:submit.prevent="onSubmit"></form>\n\n<!-- 修饰符可以串联 -->\n<a v-on:click.stop.prevent="doThat"></a>\n\n<!-- 只有修饰符 -->\n<form v-on:submit.prevent></form>\n\n<!-- 添加事件监听器时使用事件捕获模式 -->\n<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->\n<div v-on:click.capture="doThis">...</div>\n\n<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->\n<!-- 即事件不是从内部元素触发的 -->\n<div v-on:click.self="doThat">...</div>\n 数组变化侦测\n\n变更方法\n\nUI发生更新,原数组发生改变更新\npush()、pop()、shift()、unshift()、sort()、reverse()\n\n替换一个数组\n\nUI不发生更新,赋值到的是新数组\nconcat()、filter()、slice()\n 计算属性\n描述依赖响应式状态的复杂逻辑\ncomputed,方法中进行计算属性\n具有缓存,所以性能优于常规方法调用\n Class绑定\nvue中对于class的v-bind得到加强,表达式除了字符串以外,还可以是数组或者对象\n//template:\n<p :class="classObject">\n Class样式绑定\n</p>\n\n//data:\nclassObject{\n\tactive: true,\n\tdanger: false\n}\n\n//style:\n.active{\n\tfont-size:30px\n}\n.danger{\n\tcolor:red\n}\n Style绑定\nvue中对于style的v-bind得到加强,表达式除了字符串以外,还可以是数组或者对象\n//template:\n<p :style:"styleObject">\n Style样式绑定\n</p>\n\n//data:\nstyleObject{\n\tactiveColor: red,\n\tactiveSize: 30\n}\n 侦听器\n使用watch选项在每次响应式属性发生变化时触发一个函数\n//data:\nmessage: "Hello"\n\n//watch:\nmessage(newValue,oldValue){\n\t//数据发生变化,自动执行方法\n\tconsole.log("new=>"+newValue);\n\tconsole.log("old=>"+oldValue);\n}\n注意watch的方法名必须和data中数据变量名一致\n 表单输入绑定\nv-model\n将表单输入和响应式属性进行双向绑定\n提供修饰符.lazy .number .trim,对输入进行过滤\n.lazy 失去焦点才绑定\n.number 只接受数字\n.trim 去除前后空格\n 模版引用\n一般来说\n内容改变: \n属性改变: v-bind: 指令\n事件: v-on:click\n若要访问一些底层的DOM元素,可以使用ref\n<div ref:"container">\n {{ content }}\n</div>\n\nthis.$refs.container.属性\n如果没有特殊需求,不要使用ref,比较消耗性能\n 组件组成\n组件的最大优势就是可复用性\n定义在.vue文件中\n组件构成:template,script,style\n引用:\n\n在script中import\n在component中注入组件\n在template以标签形式显示组件\n\n对于一个组件,必须存在template,其他可以没有\nstyle中scoped属性表示:让当前样式只在当前组件中生效\n 组件嵌套关系\nnull\n 组件注册方式\n全局注册和局部注册\n全局注册\n在main.js中,\nimport Header from ".....";\nconst app = createApp(App);\n//在这个中间\napp.component("Header",Header)\n\napp.mount("#app")\n局部注册\n\nimport\n在components中注册\n在template中显示\n\n推荐使用局部注册\n 组件传递数据-Props\n使用props进行组件之间的数据传递\n<Child title="数据"/>\n在Child.vue中\nprops:["title"]\n即可获取title的数据值{{title}}\n也可以结合v-bind,即\n<Child :title="message"/>\n\ndata():\n\tmessage: xxx\n注意事项:props传递数据只能从父级到子级,不能反其道而行。\n 组件传递多种数据类型\n此外,props还能传递除了字符串以外的数据类型,例如数字,数组,对象等\n 组件传递Props效验\nprops{\n\ttitle{\n\t\ttype: String,\n\t\tdefault: "Alex"\n\t}\n\tnames{\n\t\ttype: [String,Number,Array,Object],\n\t\trequired: true //是否为必选项\n\t}\n}\n如果是数组或者对象,default就需要以函数形式写出来\nnames{\n\ttype:Array,\n\tdefault(){\n\t\treturn xxx;\n\t}\n}\nProps是只读的,不允许进行修改父元素传递过来的数据\n 组件事件\n$emit触发自定义事件,达到组件之间的数据传递\n在子级元素中this.$emit(“someEvent”,data)\n即可在父元素中\n<Child @someEvent="getData" />\n\nmethods:\ngetData(data){\n\tconsole.log(data);\n}\n父传子:props\n子传父:$emit\n 组件事件配合v-model\n在侦听器watch中进行$emit传递v-model数据的newValue\n 组件数据传递\n用props实现子传父\n将传递的数据类型设置为Function\n 透传Attribute\n×\n 插槽Slots\n为子组件传递一些模版片段,在子组件中渲染这些片段\nApp.vue中\n<template>\n\t<SlotA>\n \t<h3>\n hhhh\n \t</h3>\n </SlotA>\n</template>\n在SlotA.vue中\n<template>\n\t<h3>\n 原本内容\n </h3>\n\t<slot></slot>\n</template>\n在子组件中用slot渲染传入的模版片段\n\n\n插槽内容可以访问到父组件的数据域\n\n\n在没有传递插槽内容情况下,可以设置插槽的默认内容\n\n\n具名插槽:\n\n\n在插槽中设定\n<template v-slot="header">\n\n</template>\n<template v-slot="main">\n\n</template>\n即可设定\n<slot name="main"></slot>\n选定具体需要的内容\n其中v-slot可以简写为#\n 组件生命周期\n创建期,挂载期,更新期,销毁期\nbeforeCreate 组件创建之前\ncreated 组件创建之后\nbeforeMount 组件渲染之前\nmounted 组件渲染之后\nbeforeUpdate 组件更新之前\nupdated 组件更新之后\nbeforeUnmount 组件销毁之前\nunmounted 组件销毁之后\n 生命周期应用\n\n通过ref获取DOM结构\n\nmounted\n\n模拟网络请求渲染数据\n\ncreated,推荐mounted\n 动态组件\n<component :is="ComponentData"></component>\n 组件保持存活\n使用动态组件切换组件时,被切换的组件会被卸载。使用<keep-alive>强制使组件保持存活\n<keep-alive>\n\t<component :is="ComponentData"></component>\n</keep-alive>\n保持存活,不会被卸载 ,数据不会被重新初始化\n 异步组件\n<script>\n import defineAsyncComponent from 'vue'\n const ComponentB = defineAsyncComponent(()=>{\n import("/ComponentB.vue")\n })\n</script>\n 依赖注入\nprops逐级透传可以用provide和inject解决\n上级组件\nprovide{\n\tmsg: "123"\n}\n//或者\nprovide(){\n\treturn{\n message = this.message\n //获取data中数据\n\t}\n}\n子级组件\ninject:["msg"]\n即可获取msg内容{{msg}}\n只能由上到下,不能反向传递\n可以在main.js提供全局数据\napp.provide("global","123")\n Vue应用\n 应用实例\n每一个vue应用都是通过createApp函数创建\nimport {createApp} from 'vue'\nconst app = createApp({\n\t//根组件选项\n})\n一个Vue项目中,有且只有一个Vue实例对象\n 根组件\nimport App from './App.vue'\nconst app = createApp(App);\n 挂载应用\n应用实例调用了.mount()才能渲染出来,该方法接受一个容器参数\napp.mount('#app')\n<div id="app"></div>\n 公共资源\n位于.assets下存放公共资源,包括css文件,图片,字体等\n","categories":["前端"],"tags":[]},{"title":"算法基础数据结构","url":"/algorithm-data-struct/","content":" 链表与邻接表\n由于用结构体+指针比较慢,一般在面试题使用,在这里使用数组模拟链表\n\n单链表\n\ne[N]:储存链表结点的值\nne[N]:储存结点的下一个结点下标,其中空结点下标为-1\n// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点\nint head, e[N], ne[N], idx;\n\n// 初始化\nvoid init()\n{\n head = -1;\n idx = 0;\n}\n\n// 在链表头插入一个数a\nvoid insert(int a)\n{\n e[idx] = a, ne[idx] = head, head = idx ++ ;\n}\n\n//在a插到下标是k的结点后面\nvoid insert(int a,int k)\n{\n e[idx] = a, ne[idx] = ne[k], ne[k] = idx ++ ;\n}\n\n// 将头结点删除,需要保证头结点存在\nvoid remove()\n{\n head = ne[head];\n}\n\n// 将下标为k的结点的后一个点删除\nvoid remove(k)\n{\n ne[k] = ne[ne[k]];\n}\n\n双链表\n\n作用:优化某些问题\n// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点\nint e[N], l[N], r[N], idx;\n\n// 初始化\nvoid init()\n{\n //0是左端点,1是右端点\n r[0] = 1, l[1] = 0;\n idx = 2;\n}\n\n// 在节点a的右边插入一个数x\nvoid insert(int a, int x)\n{\n e[idx] = x;\n l[idx] = a, r[idx] = r[a];\n l[r[a]] = idx, r[a] = idx ++ ;\n}\n\n// 删除节点a\nvoid remove(int a)\n{\n l[r[a]] = l[a];\n r[l[a]] = r[a];\n}\n\n邻接表\n\nN个单链表,用于存储树和图\n 栈\n先进后出(FILO)\n// tt表示栈顶\nint stk[N], tt = 0;\n\n// 向栈顶插入一个数\nstk[ ++ tt] = x;\n\n// 从栈顶弹出一个数\ntt -- ;\n\n// 栈顶的值\nstk[tt];\n\n// 判断栈是否为空,如果 tt > 0,则表示不为空\nif (tt > 0)\n{\n\tnot empty\n}else\n{\n empty\n}\n 队列\n先进先出(FIFO)\n// hh 表示队头,tt表示队尾\nint q[N], hh = 0, tt = -1;\n\n// 向队尾插入一个数\nq[ ++ tt] = x;\n\n// 从队头弹出一个数\nhh ++ ;\n\n// 队头的值\nq[hh];\n\n// 判断队列是否为空,如果 hh <= tt,则表示不为空\nif (hh <= tt)\n{\n\n}\n循环队列\n// hh 表示队头,tt表示队尾的后一个位置\nint q[N], hh = 0, tt = 0;\n\n// 向队尾插入一个数\nq[tt ++ ] = x;\nif (tt == N) tt = 0;\n\n// 从队头弹出一个数\nhh ++ ;\nif (hh == N) hh = 0;\n\n// 队头的值\nq[hh];\n\n// 判断队列是否为空,如果hh != tt,则表示不为空\nif (hh != tt)\n{\n\n}\n 单调栈\n题型:求给定序列每一个数左/右边离他最近的比他大/小的数\nint tt = 0;\nfor (int i = 1; i <= n; i ++ )\n{\n while (tt && check(stk[tt], i)) tt -- ;\n stk[ ++ tt] = i;\n}\n 单调队列\n题型:求滑动窗口的最大/小值\nint hh = 0, tt = -1;\nfor (int i = 0; i < n; i ++ )\n{\n while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头是否滑出窗口\n while (hh <= tt && check(q[tt], i)) tt -- ;\n q[ ++ tt] = i;\n}\n KMP\n对于字符串sss,判断是否包含模式串ttt\n// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度\n//求模式串的Next数组:\nfor (int i = 2, j = 0; i <= m; i ++ )\n{\n while (j && p[i] != p[j + 1]) j = ne[j];\n if (p[i] == p[j + 1]) j ++ ;\n ne[i] = j;\n}\n\n// 匹配\nfor (int i = 1, j = 0; i <= n; i ++ )\n{\n while (j && s[i] != p[j + 1]) j = ne[j];\n if (s[i] == p[j + 1]) j ++ ;\n if (j == m)\n {\n j = ne[j];\n // 匹配成功后的逻辑\n }\n}\n Trie树\n快速存储和查找字符串集合的数据结构\nint son[N][26], cnt[N], idx;\n// 0号点既是根节点,又是空节点\n// son[][]存储树中每个节点的子节点\n// cnt[]存储以每个节点结尾的单词数量\n\n// 插入一个字符串\nvoid insert(char *str)\n{\n int p = 0;\n for (int i = 0; str[i]; i ++ )\n {\n int u = str[i] - 'a';\n if (!son[p][u]) son[p][u] = ++ idx;\n p = son[p][u];\n }\n cnt[p] ++ ;\n}\n\n// 查询字符串出现的次数\nint query(char *str)\n{\n int p = 0;\n for (int i = 0; str[i]; i ++ )\n {\n int u = str[i] - 'a';\n if (!son[p][u]) return 0;\n p = son[p][u];\n }\n return cnt[p];\n}\n 并查集\n\n将两个集合合并\n询问两个元素是否在一个集合当中\n\n近乎O(1)O(1)O(1)\n基本原理:每一个集合用一棵树表示,树根的编号就是整个集合的编号。每个节点存储它的父节点p[x]p[x]p[x]表示xxx的父节点\n\n如何判断是树根?\n\np[x]=xp[x] = xp[x]=x\n\n如何求xxx的集合编号\n\nwhile(p[x]!=x) x = p[x]\n\n如何合并两个区间\n\n设p[x]为x集合编号,p[y]是y集合编号。p[x]=y\n优化:路径压缩,先搜索一遍,再将节点的父节点直接指向树根\n(1)朴素并查集:\n\n int p[N]; //存储每个点的祖宗节点\n\n // 返回x的祖宗节点+路径压缩\n int find(int x)\n {\n if (p[x] != x) p[x] = find(p[x]);\n return p[x];\n }\n\n // 初始化,假定节点编号是1~n\n for (int i = 1; i <= n; i ++ ) p[i] = i;\n\n // 合并a和b所在的两个集合:\n p[find(a)] = find(b);\n\n\n(2)维护size的并查集:\n\n int p[N], size[N];\n //p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量\n\n // 返回x的祖宗节点\n int find(int x)\n {\n if (p[x] != x) p[x] = find(p[x]);\n return p[x];\n }\n\n // 初始化,假定节点编号是1~n\n for (int i = 1; i <= n; i ++ )\n {\n p[i] = i;\n size[i] = 1;\n }\n\n // 合并a和b所在的两个集合:\n size[find(b)] += size[find(a)];\n p[find(a)] = find(b);\n\n\n(3)维护到祖宗节点距离的并查集:\n\n int p[N], d[N];\n //p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离\n\n // 返回x的祖宗节点\n int find(int x)\n {\n if (p[x] != x)\n {\n int u = find(p[x]);\n d[x] += d[p[x]];\n p[x] = u;\n }\n return p[x];\n }\n\n // 初始化,假定节点编号是1~n\n for (int i = 1; i <= n; i ++ )\n {\n p[i] = i;\n d[i] = 0;\n }\n\n // 合并a和b所在的两个集合:\n p[find(a)] = find(b);\n d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量\n【模版】并查集\n并查集题单\n 堆\n如何手写一个堆?\n\n插入一个数\n求集合中的最小值\n删除最小值\n删除任意一个元素\n修改任意一个元素\n\nSTL自带堆没有实现\n是一个完全二叉树\n小根堆:根小于等于左右孩子的值\n存储:一维数组,对于iii,左孩子2i2i2i,右孩子2i+12i+12i+1\n借助down(x)down(x)down(x) 与 up(x)up(x)up(x)函数实现上述五种操作\nheap[++size] = x;up(size);\nheap[1]\nheap[1] = heap[size];size--;down(1);\nheap[k] = heap[size];size--;down(k);up(k);\nheap[k] = x;down[k];up[k]\n\n// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1\n// ph[k]存储第k个插入的点在堆中的位置\n// hp[k]存储堆中下标是k的点是第几个插入的\nint h[N], ph[N], hp[N], size;\n\n// 交换两个点,及其映射关系\nvoid heap_swap(int a, int b)\n{\n swap(ph[hp[a]],ph[hp[b]]);\n swap(hp[a], hp[b]);\n swap(h[a], h[b]);\n}\n\nvoid down(int u)\n{\n int t = u;\n if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;\n if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;\n if (u != t)\n {\n heap_swap(u, t);\n down(t);\n }\n}\n\nvoid up(int u)\n{\n while (u / 2 && h[u] < h[u / 2])\n {\n heap_swap(u, u / 2);\n u >>= 1; // u/=2\n }\n}\n\n// O(n)建堆\nfor (int i = n / 2; i; i -- ) down(i);\n包含映射的堆不常用,在dijkstra算法中应用\n 哈希表\n 存储结构\nO(1)O(1)O(1)将大范围数映射到小范围值域(离散化是特殊的hash方式)\n\nxmod rangex\\mod\\ rangexmod range\n处理冲突\n\n\n拉链法\n\n创建一维数组存储所有哈希值,若不同元素对应同一个哈希值,则在该值拉链下来(链表)\nint h[N], e[N], ne[N], idx;\n\n // 向哈希表中插入一个数\n void insert(int x)\n {\n int k = (x % N + N) % N; //防止(x % N) 是负数\n e[idx] = x;\n ne[idx] = h[k];\n h[k] = idx ++ ;\n }\n\n // 在哈希表中查询某个数是否存在\n bool find(int x)\n {\n int k = (x % N + N) % N;\n for (int i = h[k]; i != -1; i = ne[i])\n if (e[i] == x)\n return true;\n\n return false;\n }\n\n开放寻址法\n\n只开辟一个一维数组,经验长度为题目范围的2-3倍\n若应该放入的位置已经有元素,则寻找下一个可以存入的位置\nint h[N];\n\n // 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置\n int find(int x)\n {\n int t = (x % N + N) % N;\n while (h[t] != null && h[t] != x)\n {\n t ++ ;\n if (t == N) t = 0; //从头查找\n }\n return t;\n }\n其中null=0x3f3f3f3fnull = 0x3f3f3f3fnull=0x3f3f3f3f\n 字符串哈希\n将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低\n小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果\ntypedef unsigned long long ULL;\nULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64\n\n// 初始化\np[0] = 1;\nfor (int i = 1; i <= n; i ++ )\n{\n h[i] = h[i - 1] * P + str[i]; //前面的值的指数阶都加一,所以要乘以P\n p[i] = p[i - 1] * P;\n}\n\n// 计算子串 str[l ~ r] 的哈希值\nULL get(int l, int r)\n{\n return h[r] - h[l - 1] * p[r - l + 1];\n}\n C++ STL\nvector, 变长数组,倍增的思想\n size() 返回元素个数\n empty() 返回是否为空\n clear() 清空\n front()/back()\n push_back()/pop_back()\n begin()/end()\n []\n 支持比较运算,按字典序\n\npair<int, int>\n first, 第一个元素\n second, 第二个元素\n 支持比较运算,以first为第一关键字,以second为第二关键字(字典序)\n\nstring,字符串\n size()/length() 返回字符串长度\n empty()\n clear()\n substr(起始下标,(子串长度)) 返回子串\n c_str() 返回字符串所在字符数组的起始地址\n\nqueue, 队列\n size()\n empty()\n push() 向队尾插入一个元素\n front() 返回队头元素\n back() 返回队尾元素\n pop() 弹出队头元素\n\npriority_queue, 优先队列,默认是大根堆\n size()\n empty()\n push() 插入一个元素\n top() 返回堆顶元素\n pop() 弹出堆顶元素\n 定义成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;\n\nstack, 栈\n size()\n empty()\n push() 向栈顶插入一个元素\n top() 返回栈顶元素\n pop() 弹出栈顶元素\n\ndeque, 双端队列\n size()\n empty()\n clear()\n front()/back()\n push_back()/pop_back()\n push_front()/pop_front()\n begin()/end()\n []\n\nset, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列\n size()\n empty()\n clear()\n begin()/end()\n ++, -- 返回前驱和后继,时间复杂度 O(logn)\n\n set/multiset\n insert() 插入一个数\n find() 查找一个数\n count() 返回某一个数的个数\n erase()\n (1) 输入是一个数x,删除所有x O(k + logn)\n (2) 输入一个迭代器,删除这个迭代器\n lower_bound()/upper_bound()\n lower_bound(x) 返回大于等于x的最小的数的迭代器\n upper_bound(x) 返回大于x的最小的数的迭代器\n map/multimap\n insert() 插入的数是一个pair\n erase() 输入的参数是pair或者迭代器\n find()\n [] 注意multimap不支持此操作。 时间复杂度是 O(logn)\n lower_bound()/upper_bound()\n\nunordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表\n 和上面类似,增删改查的时间复杂度是 O(1)\n 不支持 lower_bound()/upper_bound(), 迭代器的++,--\n\nbitset, 圧位\n bitset<10000> s;\n ~, &, |, ^\n >>, <<\n ==, !=\n []\n\n count() 返回有多少个1\n\n any() 判断是否至少有一个1\n none() 判断是否全为0\n\n set() 把所有位置成1\n set(k, v) 将第k位变成v\n reset() 把所有位变成0\n flip() 等价于~\n flip(k) 把第k位取反\n","categories":["算法"],"tags":[]},{"title":"2023年中闲聊","url":"/2023%E5%B9%B4%E4%B8%AD%E9%97%B2%E8%81%8A/","content":"现在是2023年6月30日\n环境工程的期末考陆陆续续已经考完了,秉承不挂就行的心态\n这学期参加的两个比赛(ACM校内选拔赛+ACM软件外包创新赛)都以失败告终\n还是自己太菜了\n暑假的计划是 考研数据结构 + ACwing算法基础课,准备混一些奖项牌子\n噶油!\n","categories":["闲聊"],"tags":[]},{"title":"算法基础课1-3(杂乱)","url":"/algorithm-1.3/","content":" 双指针算法\n\n两个序列,两个指针\n一个序列,两个指针\n\n结构\nfor(int i=0,j=0;i<n;i++){\n while(j<i && check(i,j)) j++;\n //每道题具体的逻辑\n}\n核心思想\n复杂度由O(n2)O(n^2)O(n2)优化到O(n)O(n)O(n)\n先想出朴素做法,寻找i与j之间的关系,是否有单调性,进行双指针优化\n 位运算\n\nn的二进制表示中第k位数字是几\n\n(k从个位开始算0,1,2…)\n\n先把第k位移到最后一位n>>k\n看个位是几 x&1\n\n\tn>>k&1\n\nlowbit(x)\n\n\t树状数组基本操作,返回x的最后一位1\n\tx&(-x)\n\t原理:-x=(~x+1)\n 离散化\n这里特指整数离散化\n将值域大,个数少的数组映射到0,1…n的自然数\n①数组中可能有重复元素,需要去重\n②如何求出xxx离散化后的值->二分\nC++模版\nvector<int> alls; // 存储所有待离散化的值\nsort(alls.begin(), alls.end()); // 将所有值排序\nalls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素\n\n// 二分求出x对应的离散化的值\nint find(int x) // 找到第一个大于等于x的位置\n{\n int l = 0, r = alls.size() - 1;\n while (l < r)\n {\n int mid = l + r >> 1;\n if (alls[mid] >= x) r = mid;\n else l = mid + 1;\n }\n return r + 1; // 映射到1, 2, ...n\n}\n由于Java中没有uniqueuniqueunique方法,其中排序与去重的具体实现如下\n// 去重 + 排序 \nList<Integer> distinctSorterAlls = alls.stream().distinct().sorted() .collect(Collectors.toList()); \n 区间合并\n\n\n按照区间左端点排序\n\n\n判断下一个区间与当前区间的关系\n\n相交\n\n更新右端点为两个区间的maxmaxmax\n\n\n不相交\n\n将当前区间更新为不相交的这个区间\n\n\n\n\n\n\tC++模版\n// 将所有存在交集的区间合并\nvoid merge(vector<PII> &segs)\n{\n vector<PII> res;\n\n sort(segs.begin(), segs.end());\n\n int st = -2e9, ed = -2e9;\n for (auto seg : segs)\n if (ed < seg.first)\n {\n if (st != -2e9) res.push_back({st, ed});\n st = seg.first, ed = seg.second;\n }\n else ed = max(ed, seg.second);\n\n if (st != -2e9) res.push_back({st, ed});\n\n segs = res;\n}\n","categories":["算法"],"tags":[]},{"title":"前缀和与差分算法","url":"/algorithm-prefix-sum-and-difference/","content":" 前缀和与差分\n一对逆运算\n 一维前缀和\n设有一列数据a1,a2,...,an−1,an{a}_1,{a}_2,...,{a}_{n-1},{a}_na1,a2,...,an−1,an\n定义Si=a1+a2+...+ai{S}_i=a_1+a_2+...+a_iSi=a1+a2+...+ai\n一般下标从1开始,S0=0S_0=0S0=0\nSiS_iSi的初始化: Si=Si−1+aiS_i = S_{i-1}+a_iSi=Si−1+ai\n作用\n快速地求出原数组中一段区间数的和\n对于区间[l,r][l,r][l,r]\n∑i=lrai=Sr−Sl−1\\sum_{i=l}^{r}a_i = S_r-S_{l-1}∑i=lrai=Sr−Sl−1\n 二维前缀和\n对于二维数组(矩阵)(a11a12...a1ja21a22...a2j............ai1ai2...aij)\\begin{pmatrix}\n a_{11}& a_{12} & ... & a_{1j}\\\\\n a_{21}& a_{22} & ... & a_{2j} \\\\\n ...& ... & ... & ...\\\\\n a_{i1}& a_{i2} & ... & a_{ij}\n\\end{pmatrix}⎝⎜⎜⎜⎛a11a21...ai1a12a22...ai2............a1ja2j...aij⎠⎟⎟⎟⎞\nSijS_{ij}Sij代表aija_{ij}aij左上角的所有元素和\n\n\n对于点(i,j)(i,j)(i,j),其二维前缀和SijS_{ij}Sij的初始化\nSij=Si−1,j+Si,j−1−Si−1,j−1+ai,jS_{ij}=S_{i-1,j}+S_{i,j-1}-S_{i-1,j-1}+a_{i,j}Sij=Si−1,j+Si,j−1−Si−1,j−1+ai,j\n\n\n设点(x1,y1)(x_1,y_1)(x1,y1)在(x2,y2)(x_2,y_2)(x2,y2)的左上角,则两点围成的矩形中所有元素和\nS=Sx2,y2−Sx2,y1−1−Sx1−1,y2+Sx1−1,y1−1S=S_{x_2,y_2}-S_{x_2,y_1-1}-S_{x_1-1,y_2}+S_{x_1-1,y_1-1}S=Sx2,y2−Sx2,y1−1−Sx1−1,y2+Sx1−1,y1−1\n\n\n 一维差分\n对一列数据a1,a2,a3,...,aia_1,a_2,a_3,...,a_ia1,a2,a3,...,ai\n构造b1,b2,b3,...,bib_1,b_2,b_3,...,b_ib1,b2,b3,...,bi使得ai=b1+b2+...+bia_i=b_1+b_2+...+b_iai=b1+b2+...+bi\n即aaa为bbb的前缀和,bbb就是aaa的差分\n{b1=a1b2=a2−a1b3=a3−a2......bn=an−an−1\\left\\{\\begin{matrix}\nb_1=a_1\\\\\nb_2=a_2-a_1\\\\\nb_3=a_3-a_2\\\\\n ......\\\\\nb_n=a_n-a_{n-1}\n\\end{matrix}\\right.⎩⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎧b1=a1b2=a2−a1b3=a3−a2......bn=an−an−1\n作用\n若要把a1,a2,a3,...,aia_1,a_2,a_3,...,a_ia1,a2,a3,...,ai中[l,r][l,r][l,r]区间的aaa加ccc\n只需要使bl+=c,br+1−=cb_l+=c,b_{r+1}-=cbl+=c,br+1−=c\n模版\nimport java.util.Scanner;\n\npublic class Diff {\n public static void main(String[] args) {\n\n Scanner scanner = new Scanner(System.in);\n\n // 给出n数组大小和k增加次数\n int n = scanner.nextInt();\n int k = scanner.nextInt();\n\n // 搭建数组\n int[] arr = new int[n+1];\n int[] brr = new int[n+1];\n\n // 为arr赋值\n for (int i = 1; i < n+1; i++) {\n arr[i] = scanner.nextInt();\n }\n\n // 为brr赋值\n for (int i = 1; i < n+1; i++){\n brr[i] = arr[i] - arr[i-1];\n }\n\n while (k-- > 0){\n // 我们为arr的[l,r]区间加上c\n int l = scanner.nextInt();\n int r = scanner.nextInt();\n int c = scanner.nextInt();\n\n brr[l] += c;\n brr[r+1] -= c;\n }\n\n // 计算输出结果即可(这里输出的需要是由b累计出来的a)\n // 也可以使用注释代码,最后输出arr即可\n for (int i = 1; i < n+1; i++) {\n brr[i] += brr[i-1];\n //arr[i] = brr[i]+arr[i-1];\n }\n\n // 最后输出结果\n for (int i = 1; i < n+1; i++) {\n System.out.println(brr[i]);\n }\n\n }\n}\n 二维差分\n原矩阵aija_{ij}aij,差分矩阵bijb_{ij}bij\nbx1,y1+=cb_{x1,y1}+=cbx1,y1+=c\nbx2+1,y−=cb_{x2+1,y}-=cbx2+1,y−=c\nbx1,y2+1−=cb_{x1,y2+1}-=cbx1,y2+1−=c\nbx2+1,y2+1+=cb_{x2+1,y2+1}+=cbx2+1,y2+1+=c\n","categories":["算法"],"tags":[]},{"title":"高精度算法","url":"/algorithm-high-precision/","content":"JavaJavaJava中使用BigIntegerBigIntegerBigInteger和BigDecimalBigDecimalBigDecimal类实现\nC++C++C++模版\n\n高精度加法\n\n// C = A + B, A >= 0, B >= 0\nvector<int> add(vector<int> &A, vector<int> &B)\n{\n if (A.size() < B.size()) return add(B, A);\n\n vector<int> C;\n int t = 0;\n for (int i = 0; i < A.size(); i ++ )\n {\n t += A[i];\n if (i < B.size()) t += B[i];\n C.push_back(t % 10);\n t /= 10;\n }\n\n if (t) C.push_back(t);\n return C;\n}\n\n高精度减法\n\n// C = A - B, 满足A >= B, A >= 0, B >= 0\nvector<int> sub(vector<int> &A, vector<int> &B)\n{\n vector<int> C;\n for (int i = 0, t = 0; i < A.size(); i ++ )\n {\n t = A[i] - t;\n if (i < B.size()) t -= B[i];\n C.push_back((t + 10) % 10);\n if (t < 0) t = 1;\n else t = 0;\n }\n\n while (C.size() > 1 && C.back() == 0) C.pop_back();\n return C;\n}\n\n高精度乘低精度\n\n// C = A * b, A >= 0, b >= 0\nvector<int> mul(vector<int> &A, int b)\n{\n vector<int> C;\n\n int t = 0;\n for (int i = 0; i < A.size() || t; i ++ )\n {\n if (i < A.size()) t += A[i] * b;\n C.push_back(t % 10);\n t /= 10;\n }\n\n while (C.size() > 1 && C.back() == 0) C.pop_back();\n\n return C;\n}\n\n高精度除以低精度\n\n// A / b = C ... r, A >= 0, b > 0\nvector<int> div(vector<int> &A, int b, int &r)\n{\n vector<int> C;\n r = 0;\n for (int i = A.size() - 1; i >= 0; i -- )\n {\n r = r * 10 + A[i];\n C.push_back(r / b);\n r %= b;\n }\n reverse(C.begin(), C.end());\n while (C.size() > 1 && C.back() == 0) C.pop_back();\n return C;\n}\n\n高精度乘以高精度\n\ncin>>a1>>b1;\n int lena=strlen(a1);\nint lenb=strlen(b1);\n for(i=1;i<=lena;i++)a[i]=a1[lena-i]-'0';\n for(i=1;i<=lenb;i++)b[i]=b1[lenb-i]-'0';\nfor(i=1;i<=lenb;i++)\n\tfor(j=1;j<=lena;j++)\n\t\tc[i+j-1]+=a[j]*b[i];\n for(i=1;i<lena+lenb;i++)\n\tif(c[i]>9)\n\t{\n\t\tc[i+1]+=c[i]/10;\n\t\tc[i]%=10;\n\t}\nlen=lena+lenb;\n while(c[len]==0&&len>1)len--;\nA+B Problem(高精)\nA*B Problem(高精)\n","categories":["算法"],"tags":[]},{"title":"排序算法","url":"/algorithm-sort/","content":" 快速排序\n分治思想,时间复杂度$O(nlogn)-O(n^2) $\n期望时间复杂度O(nlogn)O(nlogn)O(nlogn)\n\n\n数组中找一个值xxx作为分界点(可以是arr[l]arr\\left [ l \\right ]arr[l] ,arr[r]arr\\left [ r \\right ]arr[r],arr[l+r2]arr\\left [ \\frac{l+r}{2} \\right ]arr[2l+r] 等等…)\n\n\n调整区间,使得左边的区间所有数≤\\le≤x,右边区间所有数>>>x\n\n定义两个指针分别在左右边界\niii不断右移,直到遇到arr[i]arr[i]arr[i] >x>x>x,就停下\njjj不断左移,直到遇到arr[j]≤xarr[j]\\le xarr[j]≤x,就停下\n交换arr[i]arr[i]arr[i]与arr[j]arr[j]arr[j]\n\n\n\n递归处理左右区间\n\n\n模版\npublic static void quick_sort(int q[], int l, int r){\n if (l >= r) return;\n int i = l - 1, j = r + 1, x = q[l + r >> 1];\n while (i < j){\n do i ++ ; while (q[i] < x);\n do j -- ; while (q[j] > x);\n if (i < j) {\n int t = q[i];\n q[i] = q[j];\n q[j] = t; \n }\n }\n quick_sort(q, l, j);\n quick_sort(q, j + 1, r);\n}\n 归并排序\n分治思想,O(nlogn)O(nlogn)O(nlogn)\n\n\n确定分界点 mid=l+r2mid = \\frac{l+r}{2}mid=2l+r\n\n\n递归排序leftleftleft和rightrightright\n\n\n归并:合二为一\n\n双指针指向leftleftleft和rightrightright的第一个元素\n创建一个空数组resresres存放结果\n指针比较,如果left[i]<right[j]left[i]<right[j]left[i]<right[j],则把left[i]left[i]left[i]放入resresres,iii向后移动一位,继续比较\n如果left[i]=right[j]left[i]=right[j]left[i]=right[j],则把left[i]left[i]left[i]放入resresres,以维持稳定\n\n\n\n模版\npublic static void merge_sort(int q[], int l, int r){\n if (l >= r) return;\n int mid = l + r >> 1;\n\n merge_sort(q, l, mid);\n merge_sort(q, mid + 1, r);\n\n int k = 0, i = l, j = mid + 1;\n\n while (i <= mid && j <= r)\n if (q[i] < q[j]) tmp[k ++ ] = q[i ++ ];\n else tmp[k ++ ] = q[j ++ ];\n\n while (i <= mid) tmp[k ++ ] = q[i ++ ];\n while (j <= r) tmp[k ++ ] = q[j ++ ];\n\n for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];\n}\n","categories":["算法"],"tags":[]},{"title":"二分查找与二分答案算法","url":"/algorithm-two-divided/","content":" 整数二分\n提示信息\n\n题目保证有解\n单调性\n求最大值的最小化\n\n思路\n对于区间[l,r][l,r][l,r],其中一部分满足条件check(x)=truecheck(x)=truecheck(x)=true,另一部分不满足\n\n对于寻找不满足区间的边界\n\nmid=l+r+12mid = \\frac{l+r+1}{2}mid=2l+r+1\n若check(mid)=truecheck(mid)=truecheck(mid)=true 则说明边界值在[mid,r][mid,r][mid,r]\n更新语句为l=midl = midl=mid\n若check(mid)=falsecheck(mid)=falsecheck(mid)=false 则说明边界值在[l,mid−1][l,mid-1][l,mid−1]\n更新语句为r=mid−1r = mid-1r=mid−1\n\n对于寻找满足区间的边界\n\nmid=l+r2mid = \\frac{l+r}{2}mid=2l+r\n若check(mid)=truecheck(mid)=truecheck(mid)=true 则说明边界值在[l,mid][l,mid][l,mid]\n更新语句为r=midr = midr=mid\n若check(mid)=falsecheck(mid)=falsecheck(mid)=false 则说明边界值在[mid+1,r][mid+1,r][mid+1,r]\n更新语句为l=mid+1l=mid+1l=mid+1\n模版\npublic static boolean check(int x) {/* ... */} // 检查x是否满足某种性质\n\n// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:\npublic static int bsearch_1(int l, int r)\n{\n while (l < r)\n {\n int mid = l + r >> 1;\n if (check(mid)) r = mid; // check()判断mid是否满足性质\n else l = mid + 1;\n }\n return l;\n}\n// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:\npublic static int bsearch_2(int l, int r)\n{\n while (l < r)\n {\n int mid = l + r + 1 >> 1;\n if (check(mid)) l = mid;\n else r = mid - 1;\n }\n return l;\n}\n如果l=midl=midl=mid ,最开始的midmidmid就要补上+1+1+1\n题目参考\n冶炼金属\n垦天计划\n 浮点数二分\npublic static boolean check(double x) {/* ... */} // 检查x是否满足某种性质\n\ndouble bsearch_3(double l, double r)\n{\n final double eps = 1e-6; \n // eps 表示精度,取决于题目对精度的要求\n //比需要保留的位数多2\n while (r - l > eps)\n {\n double mid = (l + r) / 2;\n if (check(mid)) r = mid;\n else l = mid;\n }\n return l;\n}\n精度比需要保留的位数多-2次方\n可以把whilewhilewhile循环直接换成for100次\n","categories":["算法"],"tags":[]},{"title":"HelloWorld","url":"/HelloWorld/","content":"把原来的一些杂乱的学习笔记都删了\n因为我觉得博客应该是记录一些自己的想法,而不是生抄别人的博客或者代码\n\n测试一下图片\npublic class Main{\n public static void init(){\n return;\n }\n}\n测试一下代码块\n咕咕咕\n","categories":["闲聊"],"tags":[]}]
\ No newline at end of file
+[{"title":"二进制相关","url":"/binary/","content":"计算机以二进制表示数据,以表示电路中的正反。在二进制下,一个位只有0和1。逢二进一位。\n计算机中存储数据,以字节为单位,一个字节有8个位,即可以表示-128~127范围的数字。\n 基础运算\n\n与\n\n用符号&表示,运算规律是:真真为真,真假为假,假假为假(一假即假)\n1&1 //1\n1&0 //0\n0&0 //0\n\n或\n\n用符号|表示,运算规律是:真真为真,真假为真,假假为假(一真即真)\n1|1 //1\n1|0 //1\n0|0 //0\n\n非\n\n运算符为~,取反的逻辑,运算规律:二进制位若为1,取反后为0。若为0,取反后为1\n~1 //11111110\n\n左移\n\n将二进制数向左移位操作,高位溢出则丢弃,低位补0\na=11;\na<<1;\n移位前:0000 1011\n移位后:0001 0110(十进制值为22)\n对一个数左移1位就是乘以2,左移n位就是乘以2的n次方(而左移运算比乘法快得多)\n\n右移\n\n右移位运算中,无符号数和有符号数的运算并不相同。对于无符号数,右移之后高位补0;对于有符号数,符号位一起移动,正数高位补0,负数高位补1\n无符号数\na=16;\na>>3;\n移位前:0001 0000\n移位后:0000 0010(十进制值为2)\n有符号数(正数)\nb=32;\na>>3;\n移位前:0010 0000\n移位后:0000 0100(十进制值位4)\n有符号数(负数)\nb=-32;\nb>>3;\n移位前:1010 0000\n移位后:1000 0100(十进制值为-4)\nc=25;\nc>>4;\n移位前:0001 1001\n移位后:0000 0001(十进制值为1)\n实际上,我们发现。右移n位就是除以2的n次方,当得到的商不是整数时会往小取整\n 正数、负数的表示\n对于负数的表示,比较特殊,其有一条运算规律:对非负数的二进制进行取反、然后+1,便可得其负数的二进制表示,以3为例子:\n00000011//3的二进制\n//~00000011 对3进行取反,得到结果\n11111100\n//对结果进行+1\n11111101//-3在计算机中的最终表示\n最终得出在一个字节下,11111101表示-3\n","categories":["计算机基础"],"tags":[]},{"title":"由数据范围反推时间复杂度及算法","url":"/algorithm-skills/","content":"一般竞赛时间限制在1s或2s,所以时间复杂度尽量控制在107−10810^7 - 10^8107−108\n下面给出在不同数据范围下,代码的时间复杂度和算法如何选择:\n\nn≤30n \\le 30n≤30,dfs+剪枝、状态压缩dp\nn≤100n \\le 100n≤100,O(n3)O(n^3)O(n3),Floyd、dp、高斯消元\nn≤1000n \\le 1000n≤1000,O(n2)O(n^2)O(n2),dp、二分、朴素Dijsktra、朴素Prim、Bellman-Ford\nn≤10000n \\le 10000n≤10000,O(nn)O(n \\sqrt n)O(nn),块状链表、分块、莫队\nn≤105n \\le 10^5n≤105,O(nlogn)O(nlogn)O(nlogn),sort、线段树、树状数组、set/map、heap、拓扑排序、堆优化Dijkstra、堆优化Prim、Kruskal、spfa、二分、CDQ分治、整体二分、后缀数组\nn≤106n \\le 10^6n≤106,O(n)O(n)O(n),单调队列、hash、双指针、BFS、并查集、kmp、AC自动机;常数比较小的O(nlogn)O(nlogn)O(nlogn),sort、树状数组、heap、dijkstra、prim\nn≤107n \\le 10^7n≤107,O(n)O(n)O(n),双指针扫描、Kmp、AC自动机、线性筛素数\nn≤109n \\le 10^9n≤109,O(n)O(\\sqrt n)O(n),判断质数\nn≤1018n \\le 10^{18}n≤1018,O(nlogn)O(nlogn)O(nlogn),最大公约数、快速幂、数位dp\nn≤101000n \\le 10^{1000}n≤101000,O((logn)2)O((logn)^2)O((logn)2),高精度加减乘除\n\n一些常见数据类型的大小\n\nlong long 内的最大阶乘 20!\nint 内的最大阶乘 12!\nint => 2312^{31}231,2∗1092*10^92∗109\nlong long => 2632^{63}263,9∗10189*10^{18}9∗1018\nfloat => 38位\ndouble => 308位\n\nmemset常赋值:-1,0,0x3f,-0x3f\n无穷大:0x3f3f3f3f\n","categories":["算法"],"tags":[]},{"title":"基于深度学习的城市港口水体BOD5稀疏矩阵软检测","url":"/paper-deep-learning-detect-BOD5/","content":"Soft detection of 5-day BOD with sparse matrix in city harbor water using deep learning techniques\n 概要\n\t对于像纽约这样的沿海城市,如何更好地控制和管理港口水质是一项重要的任务。为了实现这样的需求,管理者们和政府需要追踪关键的水质指标,例如温度、pH值和溶解氧。在这些指标当中,五日生化需氧量BOD5BOD_5BOD5是一个需要花费许多时间与精力来检测的重要指标,无论在学术上还是在工程上都造成了很大不便。现有的实验和统计方法无法有效解决检测时间问题且提供的精度有限。 此外,由于各种人为操作失误与设备误差,用于BODBODBOD检测和预测的数据包含许多缺失值,导致矩阵稀疏。很少有研究在开发统计检测方法时解决稀疏矩阵问题。为了解决这些误差,我们提出了一种基于深度学习的模型,该模型结合了深度矩阵分解DMFDMFDMF和深度神经网络DNNDNNDNN。该模型能够更加智能地解决稀疏矩阵问题,更准确地预测BODBODBOD值。为了测试其有效性,我们依据32323个水样对纽约市港口水域进行了案例研究。结果表明,该方法比传统矩阵补全方法降低了11.54%-17.23%的RMSERMSERMSE,比传统的机器学习算法降低了19.20%-25.16%的RMSERMSERMSE。\n注:\nRMSE(Root mean squared error): 剩余标准差SES_ESE,也称均方差,统计学概念,在线性回归分析中,真实值和估计值之间的差称为残差(或者剩余量),所有预测值的残差平方和(或者剩余平方和),剩余标准差就是剩余平方和的开平方。用来表示估计值的精度。)\n 1.介绍\n 1.1.背景\n\t港口水由自然或人为屏障形成,是最为重要的地表水组成之一,对于纽约、旧金山、香港和上海等城市,港口流入了大部分城市雨水和城市污水,然而在某些情况下,这些污水很少能得到处理,以至于在城市港口造成各种水污染。细菌、过量的营养物质、有毒化学物质和其他污染物会影响周边人群的健康,也危机海洋生物。为了减少其对环境的严重影响,城市管理者投入了大量精力控制和改善港口水质。例如,纽约市在过去十年投入了超过120亿美元升级城市下水道系统,减少港口水的环境影响。旧金山从2008年起已向旧金山港口水质改善基金会拨款超过5000多万美元。香港推出了不同类型的总体规划,包括保护港口用水的污染者罚款政策。\n为了更好地控制和管理港口水质,对关键的质量指标进行追踪是很重要的,例如温度、溶解氧、pH、营养素和金属。这些水质指标中许多可以通过现代传感器进行实时监测,然而,有一个关键指标不仅体现了水质的关键信息,而且需要花费大量时间和精力进行检测:生化需氧量(BOD)指标。这个指标的定义是:需氧生物在特定温度(通常为20℃)下,在特定时间段内(通常为5天,标记为BOD5BOD_5BOD5)内分解水中有机物所需要的溶解氧量。如果BOD含量过高,水中溶解氧可能被耗尽,导致需氧生物难以生存。因此,监测水质时,测量BOD5BOD_5BOD5含量是十分重要的。\n\t尽管BOD5BOD_5BOD5是一项十分有价值的指标,但是其测量时间会造成很大的限制。根据标准,BOD样品的保存时间为采集后的48小时,测试本身需要5天的潜伏期,这种时间约束导致了管理和研究的各种弊端,该问题引起了人们的广泛关注。\n 1.2 文献综述\n","categories":["论文"],"tags":[]},{"title":"贪心算法","url":"/algorithm-greedy/","content":"区间选点\n将每个区间按照右端点从小到大排序,设ed为上一个点的右端点\n遍历区间的左端点,如果左端点小于等于ed,则遍历下一个区间\n否则ed为当前区间的右端点,答案加一\n最大不相交区间数\n将每个区间按照右端点从小到大排序,设ed为上一个点的右端点\n遍历区间的左端点,如果左端点小于ed,则遍历下一个区间\n否则ed为当前区间的右端点,答案加一\n区间分组\n将区间按照左端点进行从小到大排序\n遍历每个区间,判断能否将其放在某个现有的组中(判断l[i]<maxrl[i]<max_rl[i]<maxr是否成立)\n如果不存在这样的组,则开一个新祖,将其放进去\n如果存在这样的组,将其放进去,更新当前组的maxrmax_rmaxr\n区间覆盖\n先将所有区间按左端点排序,枚举每个区间,第一次选择能覆盖s点的区间中右端点最大的区间,将s更新为右端点的最大值。\n合并果子\n选择值最小的两堆果子进行合并即可,可以用优先队列求解(记得把两者之和加回队列)\n排队打水\n\n有 n 个人在一个水龙头前排队接水,假如每个人接水的时间为TiT_iTi,请编程找出这 n 个人排队的一种顺序,使得 n 个人的平均等待时间最小。\n\nT=t1∗(n−1)+t2∗(n−2)+...+tn−2∗2+tn−1∗1T=t_1*(n-1)+t_2*(n-2)+...+t_{n-2}*2+t_{n-1}*1T=t1∗(n−1)+t2∗(n−2)+...+tn−2∗2+tn−1∗1\n所以尽可能将小的t放在前面与较大的系数相乘,即按照从小到大排队,总时间最小。\n货仓地址\n奶牛耍杂技\nAcwing中修改题干为:求最大压扁值的可能最小值,不影响求解思路及结果\n按照wi+siw_i+s_iwi+si排序,依次遍历求出压扁值的最大值,输出答案即可。\n","categories":["算法"],"tags":[]},{"title":"dp问题合集","url":"/algorithm-dp/","content":" 动态规划\n\n状态表示f[i,j]f[i,j]f[i,j]\n\n集合\n属性(Max,Min,Cnt)\n\n\n状态计算\n\n集合的划分\n\n\n\n 线性DP\n数字三角形\nO(n2)O(n^2)O(n2)\nf[i,j]f[i,j]f[i,j]表示到坐标为[i,j][i,j][i,j]的路径的和最大值\nf[1][1]=a[1][1]f[1][1] = a[1][1]f[1][1]=a[1][1]\nf[i][j]=max(f[i−1][j−1]+a[i][j],f[i−1][j]+a[i][j])f[i][j] = max(f[i-1][j-1]+a[i][j], f[i-1][j]+a[i][j])f[i][j]=max(f[i−1][j−1]+a[i][j],f[i−1][j]+a[i][j])\n最长上升子序列\n朴素:O(n2)O(n^2)O(n2)\nf[i]f[i]f[i]表示以第iii个数结尾的序列中上升子序列长度的最大值\n遍历aia_iai所有可能的前一个数aja_jaj(aj<aia_j<a_iaj<ai且0≤j≤i−10 \\le j \\le i-10≤j≤i−1)\nf[i]=max(f[j]+1,f[i]),j∈[0,i−1]f[i] = max(f[j]+1,f[i]),j \\in [0,i-1]f[i]=max(f[j]+1,f[i]),j∈[0,i−1]\n如果要保存最长序列:g[i]g[i]g[i]保存从哪一步jjj转移过来\n代码:https://www.luogu.com.cn/record/124595657\n优化:O(nlogn)O(nlogn)O(nlogn)\n用一个q数组储存长度为i的序列的结尾数字的最小值\n可以证明qi>qi−1>...>q2>q1q_i>q_{i-1}>...>q_2>q_1qi>qi−1>...>q2>q1,即数组严格单调递增\n对于aia_iai,二分找到最大的qk<=aiq_k<=a_iqk<=ai,f[i]=k+1f[i] = k+1f[i]=k+1,更新qk=aiq_k = a_iqk=ai\n代码:https://www.luogu.com.cn/record/133704642\n最长公共子序列\n朴素:O(n2)O(n^2)O(n2)\nf[i][j]f[i][j]f[i][j]表示所有在第一个序列的前i个字母中出现,且在第二个序列的前j个字母中出现的子序列的最大值\n集合划分依据:是否选择a[i],b[j]a[i],b[j]a[i],b[j]\n分为四个集合:选择a[i],b[j]a[i],b[j]a[i],b[j] ; 选择a[i]a[i]a[i] 不选择b[j]b[j]b[j] ; 不选择a[j]a[j]a[j]选择b[j]b[j]b[j] ; 都不选择a[i],b[j]a[i],b[j]a[i],b[j]\n分别表示为 f[i−1][j−1],f[i,j−1],f[i−1][j],f[i−1][j−1]+1f[i-1][j-1] , f[i,j-1] , f[i-1][j] , f[i-1][j-1]+1f[i−1][j−1],f[i,j−1],f[i−1][j],f[i−1][j−1]+1\n其中第二三种情况包含上面对应的集合(由于是求Max,所以有重复不影响结果)\n且第二三种集合也包含第一个集合,所以只要对后三种集合求最大值即可\nf[i,j]=max(f[i−1,j],f[i,j−1])f[i,j] = max(f[i-1,j],f[i,j-1])f[i,j]=max(f[i−1,j],f[i,j−1])\n当a[i]==b[j]a[i]==b[j]a[i]==b[j]时,f[i,j]=max(f[i,j],f[i−1,j−1]+1)f[i,j] = max(f[i,j],f[i-1,j-1]+1)f[i,j]=max(f[i,j],f[i−1,j−1]+1)\n优化:O(nlogn)O(nlogn)O(nlogn)\n编辑距离\nf[i,j]f[i,j]f[i,j]所有将a[1−i]a[1-i]a[1−i]变成b[1−j]b[1-j]b[1−j]的操作方式的最小步数\n区间划分,①删除最后一个数、②增加最后一个数、③修改最后一个数\n① f[i−1,j]+1f[i-1,j]+1f[i−1,j]+1\n②f[i,j−1]+1f[i,j-1]+1f[i,j−1]+1\n③f[i−1,j−1]+1f[i-1,j-1]+1f[i−1,j−1]+1 (如果a[i]==b[j]a[i]==b[j]a[i]==b[j]则不需要加一,即不需要进行修改操作)\n 区间DP\n石子合并\nf[i,j]f[i,j]f[i,j]表示将第iii堆石子到第jjj堆石子合并成一堆石子的方式的代价最小值/最大值\nO(n3)O(n^3)O(n3)\nfor(int len=2;len<=n;len++){\n for(int i=1;i+len-1<=n;i++){\n int l = i,r = i+len-1;\n f_max[l][r] = -1e8,f_min[l][r] = 1e8;\n for(int k=l;k<r;k++){\n f_max[l][r] = max(f_max[l][r],f_max[l][k]+f_max[k+1][r]+s[r]-s[l-1]);\n f_min[l][r] = min(f_min[l][r],f_min[l][k]+f_min[k+1][r]+s[r]-s[l-1]);\n }\n }\n}\n 计数类DP\n 数位统计DP\n计数问题\n设n=abcdefgn=abcdefgn=abcdefg,枚举第iii位是 x∈[0,9]x \\in [0,9]x∈[0,9]\n举例x=1,i=4x=1,i=4x=1,i=4的情况:\n设数字为xxx1yyyxxx1yyyxxx1yyy\n\n当abc>xxx,xxx∈[000,abc−1],y∈[000,999]abc>xxx,xxx \\in [000,abc-1], y \\in [000,999]abc>xxx,xxx∈[000,abc−1],y∈[000,999],则共有abc∗1000abc * 1000abc∗1000个\n当abc<xxxabc<xxxabc<xxx,则共有0个\n当abc=xxxabc=xxxabc=xxx\n\n当d<1d<1d<1,无解\n当d=1d=1d=1,yyy∈[000,efg]yyy \\in [000,efg]yyy∈[000,efg],则有efg+1efg+1efg+1种\n当d>1d>1d>1,yyy∈[000,999]yyy \\in [000,999]yyy∈[000,999],有1000种\n\n\n\n当x=0时,注意前导0,即对于第一种情况,xxx∈[001,abc−1]xxx \\in [001,abc-1]xxx∈[001,abc−1],即有(abc−1)∗1000(abc-1)*1000(abc−1)∗1000情况\n圆形数字\n 状态压缩DP\n蒙德里安的梦想\nf[i][j]f[i][j]f[i][j]表示第i列,上一列横着摆的数量j,其中j是一个二进制数。\n最短Hamilton路径\nf[i][j]f[i][j]f[i][j]表示从0号点走到j号点,走过的所有点是i的所有路径(二进制数i表示某个点是否已经走过了)的最小路径长度\n 树形DP\n没有上司的舞会\nf[u][0]f[u][0]f[u][0]表示所有以u为根的子树中选择,并且不选u这个点的方案的最大值\nf[u][1]f[u][1]f[u][1]表示所有以u为根的子树中选择,并且选u这个点的方案的最大值\n设点u的子节点s1,s2,s3....sis_1,s_2,s_3....s_is1,s2,s3....si\nf[u][0]=∑1imax(f[si][0],f[si][1])f[u][0] = \\sum_{1}^{i}max(f[s_i][0],f[s_i][1])f[u][0]=∑1imax(f[si][0],f[si][1])\nf[u][1]=∑1if[si][0]f[u][1] = \\sum_{1}^{i}f[s_i][0]f[u][1]=∑1if[si][0]\n找出根节点,递归求最大值即可\n 记忆化搜索\n滑雪\n用s[i][j]s[i][j]s[i][j]表示从(i,j)点出发能走的最长距离。\n每次搜索一次记忆一次即可。\n举例\n3 3 \n1 1 3\n2 3 4\n1 1 1\n先去找(1,1)的最长距离,很明显为1\n接着找(1,2)的最长距离,很明显为1\n接着找(1,3)的最长距离,为2((1,3)->(1,2))\n然后找(2,1)的最长距离,为2((2,1)->(1,1))\n然后是(2,2)的最长距离,如果没有记忆化,那么搜索过程为:(2,2)->(2,1)->(1,1)\n但是(2,1)之前已经搜过了,再去搜就是浪费时间,之前搜索已经知道(2,1)的值为2,那么搜索过程就是缩短为:(2,2)->(2,1),即为3\n 杂题\n【动态规划1】动态规划的引入\nkkksc03考前临时抱佛脚\n【NOIP1999】挖地雷\ndp+递归倒序输出,当然这题使用爆搜也可以过\n最大字段和\nf[i]f[i]f[i]表示iii所在的有效序列的最大字段和\n如果f[i−1]+a[i]f[i-1]+a[i]f[i−1]+a[i]更大,则加入到前一个有效序列\n否则a[i]a[i]a[i]自己为一个有效序列\n五倍经验日\n01背包的变形\n","categories":["算法"],"tags":[]},{"title":"背包问题算法","url":"/algorithm-knapsack-problem/","content":" 动态规划\n\n状态表示f[i,j]f[i,j]f[i,j]\n\n集合\n属性(Max,Min,Cnt)\n\n\n状态计算\n\n集合的划分\n\n\n\n 背包问题\n给定nnn个物品和容量vvv的背包,每个物品都有体积viv_ivi和价值wiw_iwi,求当∑i=1nvi≤v\\sum_{i=1}^{n} v_i \\le v∑i=1nvi≤v时最大的www是多少\n 01背包问题\n每个物品只能用0/1次\nf[i,j]=max(f[i−1,j],f[i−1,j−vi]+wi)f[i,j] = max(f[i-1,j],f[i-1,j-v_i]+w_i)f[i,j]=max(f[i−1,j],f[i−1,j−vi]+wi)\n01背包问题\n采药\n 完全背包问题\n物品可以无限次使用\nf[i,j]=Max(f[i−1,j−vi×k]+w[i]×k)f[i,j] = Max(f[i-1,j-v_i \\times k]+w[i] \\times k)f[i,j]=Max(f[i−1,j−vi×k]+w[i]×k)\nk⊆[0,jvi]k \\subseteq [0,\\frac{j}{v_i}]k⊆[0,vij]\n即f[i,j]=Max(f[i−1,j],f[i−1,j−vi]+wi,f[i−1,j−2vi]+2wi,....,f[i−1][j−kvi]+kwi)f[i,j] = Max(f[i-1,j],f[i-1,j-v_i]+w_i,f[i-1,j-2v_i]+2w_i,....,f[i-1][j-kv_i]+kw_i)f[i,j]=Max(f[i−1,j],f[i−1,j−vi]+wi,f[i−1,j−2vi]+2wi,....,f[i−1][j−kvi]+kwi)\nf[i,j−vi]=Max(f[i−1][j−vi],f[i−1][j−2vi]+wi,...,f[i−1][j−kvi]+(k−1)wi)f[i,j-v_i] = Max(f[i-1][j-v_i],f[i-1][j-2v_i]+w_i,...,f[i-1][j-kv_i]+(k-1)w_i)f[i,j−vi]=Max(f[i−1][j−vi],f[i−1][j−2vi]+wi,...,f[i−1][j−kvi]+(k−1)wi)\nf[i][j]f[i][j]f[i][j]的后kkk项等于f[i][j−vi]+wif[i][j-v_i]+w_if[i][j−vi]+wi\n得\nf[i,j]=Max(f[i−1,j],f[i,j−vi]+wi)f[i,j] = Max(f[i-1,j],f[i,j-v_i]+w_i)f[i,j]=Max(f[i−1,j],f[i,j−vi]+wi)\n完全背包问题\n 多重背包物品\n每个物品的个数不一致\n朴素做法\nf[i,j]=Max(f[i−1,j],f[i−1,j−vi]+wi,f[i−1,j−2vi]+2wi,....,f[i−1][j−kvi]+kwi)f[i,j] = Max(f[i-1,j],f[i-1,j-v_i]+w_i,f[i-1,j-2v_i]+2w_i,....,f[i-1][j-kv_i]+kw_i)f[i,j]=Max(f[i−1,j],f[i−1,j−vi]+wi,f[i−1,j−2vi]+2wi,....,f[i−1][j−kvi]+kwi)\nk⊆[0,si]k \\subseteq [0,s_i]k⊆[0,si]\n三重循环即可\n多重背包问题1\n优化:二进制优化\nfor(int i=1;i<=n;i++){\n int a,b,s;\n cin>>a>>b>>s;\n //v w s;\n int k = 1;\n while(k<=s){\n cnt++;\n v[cnt] = a*k;\n w[cnt] = b*k;\n s-=k;\n k*=2;\n }\n if(s>0){\n cnt++;\n v[cnt] = a*s;\n w[cnt] = b*s;\n }\n}\n对物品进行二进制分组,组数为cntcntcnt,转化为01背包问题求解\nn = cnt;\nfor(int i=1;i<=n;i++){\n for(int j=0;j<=m;j++){\n f[i][j] = f[i-1][j];\n if(j>=v[i]) f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);\n }\n}\ncout<<f[n][m]<<endl;\n多重背包问题2\n 分组背包问题\n有NNN组,每一组只能选其中一种物品\nf[i][j]=Max(f[i−1,j],f[i−1,j−vi,k]+wi,k)f[i][j] = Max(f[i-1,j],f[i-1,j-v_{i,k}]+w_{i,k})f[i][j]=Max(f[i−1,j],f[i−1,j−vi,k]+wi,k)\n分组背包问题\n","categories":["算法"],"tags":[]},{"title":"算法数论","url":"/algorithm-math/","content":" 质数\n对于大于一的整数,如果只包含一和本身这两个约数,它就是质数(也叫素数)\n 试除法\nO(n)O(\\sqrt n)O(n)\nbool is_prime(int x)\n{\n if (x < 2) return false;\n for (int i = 2; i <= x / i; i ++ )\n if (x % i == 0)\n return false;\n return true;\n}\n 试除法分解质因数\nvoid divide(int x)\n{\n for (int i = 2; i <= x / i; i ++ )\n if (x % i == 0)\n {\n int s = 0;\n while (x % i == 0) x /= i, s ++ ;\n cout << i << ' ' << s << endl;\n }\n if (x > 1) cout << x << ' ' << 1 << endl;\n cout << endl;\n}\n 朴素筛法求素数\n枚举每一个数,如果它没有被筛,则加入质数集合,并且把它的所有倍数都筛掉\nO(nloglogn)O(nloglogn)O(nloglogn)\nint primes[N], cnt; // primes[]存储所有素数\nbool st[N]; // st[x]存储x是否被筛掉\n\nvoid get_primes(int n)\n{\n for (int i = 2; i <= n; i ++ )\n {\n if (st[i]) continue;\n primes[cnt ++ ] = i;\n for (int j = i + i; j <= n; j += i)\n st[j] = true;\n }\n}\n 线性筛法求素数\nint primes[N], cnt; // primes[]存储所有素数\nbool st[N]; // st[x]存储x是否被筛掉\n\nvoid get_primes(int n)\n{\n for (int i = 2; i <= n; i ++ )\n {\n if (!st[i]) primes[cnt ++ ] = i;\n for (int j = 0; primes[j] <= n / i; j ++ )\n {\n st[primes[j] * i] = true;\n if (i % primes[j] == 0) break;\n }\n }\n}\n 约数\n 试除法求所有约数\nvector<int> get_divisors(int x)\n{\n vector<int> res;\n for (int i = 1; i <= x / i; i ++ )\n if (x % i == 0)\n {\n res.push_back(i);\n if (i != x / i) res.push_back(x / i);\n }\n sort(res.begin(), res.end());\n return res;\n}\n 约数个数和约数之和\n如果 N = p1^c1 * p2^c2 * … *pk^ck\n约数个数: (c1 + 1) * (c2 + 1) * … * (ck + 1)\n约数之和: (p1^0 + p1^1 + … + p1^c1) * … * (pk^0 + pk^1 + … + pk^ck)\n 欧几里得算法求最大公约数\nint gcd(int a, int b)\n{\n return b ? gcd(b, a % b) : a;\n}\n 欧拉函数\nϕ(n)\\phi(n)ϕ(n):1-n中与n互质的数的个数\nint phi(int x)\n{\n int res = x;\n for (int i = 2; i <= x / i; i ++ )\n if (x % i == 0)\n {\n res = res / i * (i - 1);\n while (x % i == 0) x /= i;\n }\n if (x > 1) res = res / x * (x - 1);\n\n return res;\n}\n 筛法求欧拉函数\nint primes[N], cnt; // primes[]存储所有素数\nint euler[N]; // 存储每个数的欧拉函数\nbool st[N]; // st[x]存储x是否被筛掉\n\n\nvoid get_eulers(int n)\n{\n euler[1] = 1;\n for (int i = 2; i <= n; i ++ )\n {\n if (!st[i])\n {\n primes[cnt ++ ] = i;\n euler[i] = i - 1;\n }\n for (int j = 0; primes[j] <= n / i; j ++ )\n {\n int t = primes[j] * i;\n st[t] = true;\n if (i % primes[j] == 0)\n {\n euler[t] = euler[i] * primes[j];\n break;\n }\n euler[t] = euler[i] * (primes[j] - 1);\n }\n }\n}\n 快速幂\n在O(logk)O(logk)O(logk)时间内求出求出akmodpa^k mod pakmodp\nint qmi(int m, int k, int p)\n{\n int res = 1 % p, t = m;\n while (k)\n {\n if (k&1) res = res * t % p;\n t = t * t % p;\n k >>= 1;\n }\n return res;\n}\n 扩展欧几里得算法\n// 求x, y,使得ax + by = gcd(a, b)\nint exgcd(int a, int b, int &x, int &y)\n{\n if (!b)\n {\n x = 1; y = 0;\n return a;\n }\n int d = exgcd(b, a % b, y, x);\n y -= (a/b) * x;\n return d;\n}\n 中国剩余定理\n给定一些两两互质的数,求解线性同余方程组\nToBeContinueTo Be ContinueToBeContinue\n 高斯消元\n在O(n3)O(n^3)O(n3)内求解线性方程组\n// a[N][N]是增广矩阵\nint gauss()\n{\n int c, r;\n for (c = 0, r = 0; c < n; c ++ )\n {\n int t = r;\n for (int i = r; i < n; i ++ ) // 找到绝对值最大的行\n if (fabs(a[i][c]) > fabs(a[t][c]))\n t = i;\n\n if (fabs(a[t][c]) < eps) continue;\n\n for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]); // 将绝对值最大的行换到最顶端\n for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c]; // 将当前行的首位变成1\n for (int i = r + 1; i < n; i ++ ) // 用当前行将下面所有的列消成0\n if (fabs(a[i][c]) > eps)\n for (int j = n; j >= c; j -- )\n a[i][j] -= a[r][j] * a[i][c];\n\n r ++ ;\n }\n\n if (r < n)\n {\n for (int i = r; i < n; i ++ )\n if (fabs(a[i][n]) > eps)\n return 2; // 无解\n return 1; // 有无穷多组解\n }\n`\n for (int i = n - 1; i >= 0; i -- )\n for (int j = i + 1; j < n; j ++ )\n a[i][n] -= a[i][j] * a[j][n];\n\n return 0; // 有唯一解\n}\n 组合数\n 递推法求组合数\nCab=Ca−1b+Ca−1b−1C_{a}^{b} = C_{a-1}^{b} + C_{a-1}^{b-1}Cab=Ca−1b+Ca−1b−1\n// c[a][b] 表示从a个苹果中选b个的方案数\nfor (int i = 0; i < N; i ++ )\n for (int j = 0; j <= i; j ++ )\n if (!j) c[i][j] = 1;\n else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;\n","categories":["算法"],"tags":[]},{"title":"最小生成树与二分图算法","url":"/algorithm-minimum-spanning-tree/","content":" 最小生成树\n Prim算法\n处理稠密图\n朴素Prim算法 O(n2)O(n^2)O(n2)\n类似dijkstra,找出距离集合最短的点,加入集合,更新其他点到集合的距离\nint n; // n表示点数\nint g[N][N]; // 邻接矩阵,存储所有边\nint dist[N]; // 存储其他点到当前最小生成树的距离\nbool st[N]; // 存储每个点是否已经在生成树中\n\n\n// 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和\nint prim()\n{\n memset(dist, 0x3f, sizeof dist);\n\n int res = 0;\n for (int i = 0; i < n; i ++ )\n {\n int t = -1;\n for (int j = 1; j <= n; j ++ )\n if (!st[j] && (t == -1 || dist[t] > dist[j]))\n t = j;\n\n if (i && dist[t] == INF) return INF;\n\n if (i) res += dist[t];\n st[t] = true;\n\n for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);\n }\n\n return res;\n}\n堆优化Prim O(mlogn)O(mlogn)O(mlogn) (不常用)\n Kruskal算法\nO(mlogm)O(mlogm)O(mlogm) 处理稀疏图\n\n将所有边按照权重排序\n枚举每条边a->b,权重c (如果ab不联通,则将边ab加入集合中)\n\nint n, m; // n是点数,m是边数\nint p[N]; // 并查集的父节点数组\n\nstruct Edge // 存储边\n{\n int a, b, w;\n\n bool operator< (const Edge &W)const\n {\n return w < W.w;\n }\n}edges[M];\n\nint find(int x) // 并查集核心操作\n{\n if (p[x] != x) p[x] = find(p[x]);\n return p[x];\n}\n\nint kruskal()\n{\n sort(edges, edges + m);\n\n for (int i = 1; i <= n; i ++ ) p[i] = i; // 初始化并查集\n\n int res = 0, cnt = 0;\n for (int i = 0; i < m; i ++ )\n {\n int a = edges[i].a, b = edges[i].b, w = edges[i].w;\n\n a = find(a), b = find(b);\n if (a != b) // 如果两个连通块不连通,则将这两个连通块合并\n {\n p[a] = b;\n res += w;\n cnt ++ ;\n }\n }\n\n if (cnt < n - 1) return INF;\n return res;\n}\n【模版】最小生成树\n买礼物\n 二分图\n定义:设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图\n一个图是二分图当且仅当图中不含有奇数环\n 染色法\nO(n+m)O(n+m)O(n+m) 判断是否为二分图\nint n; // n表示点数\nint h[N], e[M], ne[M], idx; // 邻接表存储图\nint color[N]; // 表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色\n\n// 参数:u表示当前节点,c表示当前点的颜色\nbool dfs(int u, int c)\n{\n color[u] = c;\n for (int i = h[u]; i != -1; i = ne[i])\n {\n int j = e[i];\n if (color[j] == -1)\n {\n if (!dfs(j, !c)) return false;\n }\n else if (color[j] == c) return false;\n }\n\n return true;\n}\n\nbool check()\n{\n memset(color, -1, sizeof color);\n bool flag = true;\n for (int i = 1; i <= n; i ++ )\n if (color[i] == -1)\n if (!dfs(i, 0))\n {\n flag = false;\n break;\n }\n return flag;\n}\n 匈牙利算法\nO(mn)O(mn)O(mn),实际运行时间远小于O(mn)O(mn)O(mn)\n实现二分图的最大匹配\nint n1, n2; // n1表示第一个集合中的点数,n2表示第二个集合中的点数\nint h[N], e[M], ne[M], idx; // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边\nint match[N]; // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个\nbool st[N]; // 表示第二个集合中的每个点是否已经被遍历过\n\nbool find(int x)\n{\n for (int i = h[x]; i != -1; i = ne[i])\n {\n int j = e[i];\n if (!st[j])\n {\n st[j] = true;\n if (match[j] == 0 || find(match[j]))\n {\n match[j] = x;\n return true;\n }\n }\n }\n\n return false;\n}\n\n// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点\nint res = 0;\nfor (int i = 1; i <= n1; i ++ )\n{\n memset(st, false, sizeof st);\n if (find(i)) res ++ ;\n}\n","categories":["算法"],"tags":[]},{"title":"最短路算法","url":"/algorithm-shortest-path/","content":" 最短路\nnnn为点数,mmm为边数\n若mmm与n2n^2n2同一级别为稠密图,与nnn同一级别为稀疏图\n稠密图使用邻接矩阵储存,稀疏图用邻接表储存\n\n单源最短路\n\n所有边权都是正数\n\n朴素dijkstradijkstradijkstra算法 O(n2+m)O(n^2+m)O(n2+m)\n堆优化版dijkstradijkstradijkstra算法 O(mlogn)O(mlogn)O(mlogn)\n\n\n存在负权边\n\nBellman−FordBellman-FordBellman−Ford算法 O(nm)O(nm)O(nm)\nSPFASPFASPFA算法 一般O(m)O(m)O(m),最坏O(nm)O(nm)O(nm)\n\n\n\n\n多源汇最短路\n\nfloydfloydfloyd算法 O(n3)O(n^3)O(n3)\n\n\n\n 朴素dijkstra算法\n\n\n初始化距离,$dist[1]=0,dist[i]=+\\infty $,st数组:当前已经确定最短路径的点\n\n\n循环每一个点,找到不在st中的最短距离点t,t加入到st中,用t更新其他点的距离\n\n\nint g[N][N]; // 存储每条边\nint dist[N]; // 存储1号点到每个点的最短距离\nbool st[N]; // 存储每个点的最短路是否已经确定\n\n// 求1号点到n号点的最短路,如果不存在则返回-1\nint dijkstra()\n{\n memset(dist, 0x3f, sizeof dist);\n dist[1] = 0;\n\n for (int i = 0; i < n - 1; i ++ )\n {\n int t = -1; // 在还未确定最短路的点中,寻找距离最小的点\n for (int j = 1; j <= n; j ++ )\n if (!st[j] && (t == -1 || dist[t] > dist[j]))\n t = j;\n\n // 用t更新其他点的距离\n for (int j = 1; j <= n; j ++ )\n dist[j] = min(dist[j], dist[t] + g[t][j]);\n\n st[t] = true;\n }\n\n if (dist[n] == 0x3f3f3f3f) return -1;\n return dist[n];\n}\n一般边数m比较多,所以使用邻接矩阵g[a][b]存储\n 堆优化版dijkstra算法\n将寻找距离最小的点的时间复杂度降低\n堆可以使用手写堆或优先队列\ntypedef pair<int, int> PII;\n\nint n; // 点的数量\nint h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边\nint dist[N]; // 存储所有点到1号点的距离\nbool st[N]; // 存储每个点的最短距离是否已确定\nvoid add(int a, int b, int c){\n e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;\n}\n// 求1号点到n号点的最短距离,如果不存在,则返回-1\nint dijkstra()\n{\n memset(dist, 0x3f, sizeof dist);\n dist[1] = 0;\n priority_queue<PII, vector<PII>, greater<PII>> heap;\n heap.push({0, 1}); // first存储距离,second存储节点编号\n\n while (heap.size())\n {\n auto t = heap.top();\n heap.pop();\n\n int ver = t.second, distance = t.first;\n\n if (st[ver]) continue;\n st[ver] = true;\n\n for (int i = h[ver]; i != -1; i = ne[i])\n {\n int j = e[i];\n if (dist[j] > distance + w[i])\n {\n dist[j] = distance + w[i];\n heap.push({dist[j], j});\n }\n }\n }\n\n if (dist[n] == 0x3f3f3f3f) return -1;\n return dist[n];\n}\n Bellman-Ford算法\n迭代n次,每次遍历所有边,对dist[b]进行更新\nint n, m; // n表示点数,m表示边数\nint dist[N]; // dist[x]存储1到x的最短路距离\n\nstruct Edge // 边,a表示出点,b表示入点,w表示边的权重\n{\n int a, b, w;\n}edges[M];\n\n// 求1到n的最短路距离,如果无法从1走到n,则返回-1。\nint bellman_ford()\n{\n memset(dist, 0x3f, sizeof dist);\n dist[1] = 0;\n\n // 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。\n for (int i = 0; i < n; i ++ )\n {\n for (int j = 0; j < m; j ++ )\n {\n int a = edges[j].a, b = edges[j].b, w = edges[j].w;\n if (dist[b] > dist[a] + w)\n dist[b] = dist[a] + w;\n }\n }\n\n if (dist[n] > 0x3f3f3f3f / 2) return -1;\n return dist[n];\n}\n//遍历完后满足三角不等式\ndist[b] <= dist[a] + w\n可以用于找负环,时间复杂度比较高\n SPFA算法\n队列优化的Bellman-Ford算法\nint n; // 总点数\nint h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边\nint dist[N]; // 存储每个点到1号点的最短距离\nbool st[N]; // 存储每个点是否在队列中\n\n// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1\nint spfa()\n{\n memset(dist, 0x3f, sizeof dist);\n dist[1] = 0;\n\n queue<int> q;\n q.push(1);\n st[1] = true;\n\n while (q.size())\n {\n auto t = q.front();\n q.pop();\n\n st[t] = false;\n\n for (int i = h[t]; i != -1; i = ne[i])\n {\n int j = e[i];\n if (dist[j] > dist[t] + w[i])\n {\n dist[j] = dist[t] + w[i];\n if (!st[j]) // 如果队列中已存在j,则不需要将j重复插入\n {\n q.push(j);\n st[j] = true;\n }\n }\n }\n }\n\n if (dist[n] == 0x3f3f3f3f) return -1;\n return dist[n];\n}\n推荐使用,只要不被卡或者存在负环\n判断负环\n在进行更新dist[j] = dist[t] + w时,同时维护cnt[x] (1号点到x号点经过的边数)\ncnt[x] = cnt[t]+1;\n若某个cnt[x] 大于等于n,则说明存在负环\nint n; // 总点数\nint h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边\nint dist[N], cnt[N]; // dist[x]存储1号点到x的最短距离,cnt[x]存储1到x的最短路中经过的点数\nbool st[N]; // 存储每个点是否在队列中\n\n// 如果存在负环,则返回true,否则返回false。\nbool spfa()\n{\n // 不需要初始化dist数组\n // 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。\n\n queue<int> q;\n for (int i = 1; i <= n; i ++ )\n {\n q.push(i);\n st[i] = true;\n }\n\n while (q.size())\n {\n auto t = q.front();\n q.pop();\n\n st[t] = false;\n\n for (int i = h[t]; i != -1; i = ne[i])\n {\n int j = e[i];\n if (dist[j] > dist[t] + w[i])\n {\n dist[j] = dist[t] + w[i];\n cnt[j] = cnt[t] + 1;\n if (cnt[j] >= n) return true; // 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环\n if (!st[j])\n {\n q.push(j);\n st[j] = true;\n }\n }\n }\n }\n\n return false;\n}\n【模版】单源最短路径(弱化版)\n【模版】单源最短路径(标准版)\n Floyd算法\n基于动态规划\n初始化:\n for (int i = 1; i <= n; i ++ )\n for (int j = 1; j <= n; j ++ )\n if (i == j) d[i][j] = 0;\n else d[i][j] = INF;\n\n// 算法结束后,d[a][b]表示a到b的最短距离\nvoid floyd()\n{\n for (int k = 1; k <= n; k ++ )\n for (int i = 1; i <= n; i ++ )\n for (int j = 1; j <= n; j ++ )\n d[i][j] = min(d[i][j], d[i][k] + d[k][j]);\n}\n【模板】Floyd 算法\n","categories":["算法"],"tags":[]},{"title":"递归与搜索","url":"/algorithm-search/","content":" DFS与BFS\n\n深度优先搜索(DFS)\n\n用StackStackStack递归,空间O(h)O(h)O(h),不具有最短性\n题目:全排列、八皇后\n\n宽度优先搜索(BFS)\n\n用QueueQueueQueue,空间O(2h)O(2^h)O(2h),“最短路”\n题目:迷宫\n回溯、剪枝\n烤鸡\n猫猫和企鹅\n在矩阵中4个方向遍历\nint dx[] = {1,0,-1,0},y = {0,1,0,-1};\n防止走相反的方向导致搜索回溯\nif(i ^ 2 == d) continue;\n8个方向遍历\nint dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};\nint dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};\n防止走相反的方向导致搜索回溯\nif(i ^ 4 == d) continue;\n 树和图的存储\n树是特殊的无环连通图\n有向图a→ba \\to ba→b\n\n\n邻接矩阵 g[a][b]g[a][b]g[a][b]\n\n\n邻接表,用链表储存点iii可以到达的点\n\n\n// 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点\nint h[N], e[N], ne[N], idx;\n\n// 添加一条边a->b\nvoid add(int a, int b)\n{\n e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;\n}\n\n// 初始化\nidx = 0;\nmemset(h, -1, sizeof h);\n 树和图的遍历\n时间复杂度O(n+m)O(n+m)O(n+m),n表示点数,m表示边数\n\n深度优先遍历\n\nint dfs(int u)\n{\n st[u] = true; // st[u] 表示点u已经被遍历过\n\n for (int i = h[u]; i != -1; i = ne[i])\n {\n int j = e[i];\n if (!st[j]) dfs(j);\n }\n}\n\n宽度优先遍历\n\nqueue<int> q;\nst[1] = true; // 表示1号点已经被遍历过\nq.push(1);\n\nwhile (q.size())\n{\n int t = q.front();\n q.pop();\n\n for (int i = h[t]; i != -1; i = ne[i])\n {\n int j = e[i];\n if (!st[j])\n {\n st[j] = true; // 表示点j已经被遍历过\n q.push(j);\n }\n }\n}\n 拓扑排序\n时间复杂度O(n+m)O(n+m)O(n+m),n表示点数,m表示边数\n有向无环图一定可以拓扑排序,序列可能不唯一\n入度、出度:有多少条边指向自己/从自己这里指出去\n\n\n将入度为0的点入队\n\n\n宽搜,枚举队头的所有出边t→jt \\to jt→j,删掉t→jt \\to jt→j,ttt的出度减一\n\n\nbool topsort()\n{\n int hh = 0, tt = -1;\n\n // d[i] 存储点i的入度\n for (int i = 1; i <= n; i ++ )\n if (!d[i])\n q[ ++ tt] = i;\n\n while (hh <= tt)\n {\n int t = q[hh ++ ];\n\n for (int i = h[t]; i != -1; i = ne[i])\n {\n int j = e[i];\n d[j]--;\n if (d[j] == 0)\n q[ ++ tt] = j;\n }\n }\n\n // 如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。\n return tt == n - 1;\n}\n一个有向无环图至少有一个入度为0的点\n","categories":["算法"],"tags":[]},{"title":"Vue学习笔记","url":"/vue/","content":" 代码风格\nVue2:选项式API\nVue3:组合式API\n 准备工作\n首先配置Node.jsNode.jsNode.js环境,然后\nnpm init vue@latest\n初始化名称namenamename(不要用大写字母)\n一路勾选NONONO\nIDE使用VSCodeVSCodeVSCode+VolarVolarVolar插件\n进入namenamename文件夹cd name\n执行npm install\nnpm run dev即可运行项目\n 文件目录\nnode_modules 依赖文件夹\npublic 资源文件夹\nsrc 源码文件夹\nindex.html 入口HTML文件\npackage.json 信息描述文件\nvite.config.js Vue配置文件\n 模版语法\n\n文本插值\n\n\t{{msg}},其中可以使用JavaScrpitJavaScrpitJavaScrpit表达式\n\t{{number +1}} 、{{ok ? 'Yes' : 'No'}}、{{msg.split('')}}\n\n原始html\n\n\t使用v-html\n 属性绑定\nv-bind\n文本的绑定使用{{}}绑定,而属性(id、class等)的绑定使用v-bind\n<div v-bind:id="ID" v-bind:class="MyClass">\n {{msg}}\n</div>\n其中ID、MyClass、msg都是data变量\n简写::属性\n例如\n<div :id="ID" :class="MyClass">\n {{msg}}\n</div>\n将属性存进同一个对象可以实现一次性绑定多个值\n 条件渲染\nv-if v-else v-else-if v-show\nv-if:如果初次渲染时为false,就不会被渲染,有较高的切换开销\nv-show:无论初始值如何都会被渲染,后续根据CSS的display属性切换,有较高的初始渲染开销\n如果需要频繁切换,用v-show\n 列表渲染\nv-for指令基于item in items\n如果要获取下标,使用(item,index) in items\n对于对象数组,则是(value,key,index)\n 通过key管理状态\nVue默认按照“就地更新”,重新渲染消耗性能\n不推荐Key绑定index,而是绑定id等唯一索引属性\n 事件处理\n使用v-on或者@监听DOM事件\n\n内联事件处理器:内联JS语句\n方法事件处理器:指向方法\n\n 事件参数\n在方法中传递参数到事件中\nadd(count){\n\tthis.num += count;\n}\n传递$event获取JS的event对象\n 事件修饰符\n简化代码\n文档\n<!-- 阻止单击事件继续传播 -->\n<a v-on:click.stop="doThis"></a>\n\n<!-- 提交事件不再重载页面 -->\n<form v-on:submit.prevent="onSubmit"></form>\n\n<!-- 修饰符可以串联 -->\n<a v-on:click.stop.prevent="doThat"></a>\n\n<!-- 只有修饰符 -->\n<form v-on:submit.prevent></form>\n\n<!-- 添加事件监听器时使用事件捕获模式 -->\n<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->\n<div v-on:click.capture="doThis">...</div>\n\n<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->\n<!-- 即事件不是从内部元素触发的 -->\n<div v-on:click.self="doThat">...</div>\n 数组变化侦测\n\n变更方法\n\nUI发生更新,原数组发生改变更新\npush()、pop()、shift()、unshift()、sort()、reverse()\n\n替换一个数组\n\nUI不发生更新,赋值到的是新数组\nconcat()、filter()、slice()\n 计算属性\n描述依赖响应式状态的复杂逻辑\ncomputed,方法中进行计算属性\n具有缓存,所以性能优于常规方法调用\n Class绑定\nvue中对于class的v-bind得到加强,表达式除了字符串以外,还可以是数组或者对象\n//template:\n<p :class="classObject">\n Class样式绑定\n</p>\n\n//data:\nclassObject{\n\tactive: true,\n\tdanger: false\n}\n\n//style:\n.active{\n\tfont-size:30px\n}\n.danger{\n\tcolor:red\n}\n Style绑定\nvue中对于style的v-bind得到加强,表达式除了字符串以外,还可以是数组或者对象\n//template:\n<p :style:"styleObject">\n Style样式绑定\n</p>\n\n//data:\nstyleObject{\n\tactiveColor: red,\n\tactiveSize: 30\n}\n 侦听器\n使用watch选项在每次响应式属性发生变化时触发一个函数\n//data:\nmessage: "Hello"\n\n//watch:\nmessage(newValue,oldValue){\n\t//数据发生变化,自动执行方法\n\tconsole.log("new=>"+newValue);\n\tconsole.log("old=>"+oldValue);\n}\n注意watch的方法名必须和data中数据变量名一致\n 表单输入绑定\nv-model\n将表单输入和响应式属性进行双向绑定\n提供修饰符.lazy .number .trim,对输入进行过滤\n.lazy 失去焦点才绑定\n.number 只接受数字\n.trim 去除前后空格\n 模版引用\n一般来说\n内容改变: \n属性改变: v-bind: 指令\n事件: v-on:click\n若要访问一些底层的DOM元素,可以使用ref\n<div ref:"container">\n {{ content }}\n</div>\n\nthis.$refs.container.属性\n如果没有特殊需求,不要使用ref,比较消耗性能\n 组件组成\n组件的最大优势就是可复用性\n定义在.vue文件中\n组件构成:template,script,style\n引用:\n\n在script中import\n在component中注入组件\n在template以标签形式显示组件\n\n对于一个组件,必须存在template,其他可以没有\nstyle中scoped属性表示:让当前样式只在当前组件中生效\n 组件嵌套关系\nnull\n 组件注册方式\n全局注册和局部注册\n全局注册\n在main.js中,\nimport Header from ".....";\nconst app = createApp(App);\n//在这个中间\napp.component("Header",Header)\n\napp.mount("#app")\n局部注册\n\nimport\n在components中注册\n在template中显示\n\n推荐使用局部注册\n 组件传递数据-Props\n使用props进行组件之间的数据传递\n<Child title="数据"/>\n在Child.vue中\nprops:["title"]\n即可获取title的数据值{{title}}\n也可以结合v-bind,即\n<Child :title="message"/>\n\ndata():\n\tmessage: xxx\n注意事项:props传递数据只能从父级到子级,不能反其道而行。\n 组件传递多种数据类型\n此外,props还能传递除了字符串以外的数据类型,例如数字,数组,对象等\n 组件传递Props效验\nprops{\n\ttitle{\n\t\ttype: String,\n\t\tdefault: "Alex"\n\t}\n\tnames{\n\t\ttype: [String,Number,Array,Object],\n\t\trequired: true //是否为必选项\n\t}\n}\n如果是数组或者对象,default就需要以函数形式写出来\nnames{\n\ttype:Array,\n\tdefault(){\n\t\treturn xxx;\n\t}\n}\nProps是只读的,不允许进行修改父元素传递过来的数据\n 组件事件\n$emit触发自定义事件,达到组件之间的数据传递\n在子级元素中this.$emit(“someEvent”,data)\n即可在父元素中\n<Child @someEvent="getData" />\n\nmethods:\ngetData(data){\n\tconsole.log(data);\n}\n父传子:props\n子传父:$emit\n 组件事件配合v-model\n在侦听器watch中进行$emit传递v-model数据的newValue\n 组件数据传递\n用props实现子传父\n将传递的数据类型设置为Function\n 透传Attribute\n×\n 插槽Slots\n为子组件传递一些模版片段,在子组件中渲染这些片段\nApp.vue中\n<template>\n\t<SlotA>\n \t<h3>\n hhhh\n \t</h3>\n </SlotA>\n</template>\n在SlotA.vue中\n<template>\n\t<h3>\n 原本内容\n </h3>\n\t<slot></slot>\n</template>\n在子组件中用slot渲染传入的模版片段\n\n\n插槽内容可以访问到父组件的数据域\n\n\n在没有传递插槽内容情况下,可以设置插槽的默认内容\n\n\n具名插槽:\n\n\n在插槽中设定\n<template v-slot="header">\n\n</template>\n<template v-slot="main">\n\n</template>\n即可设定\n<slot name="main"></slot>\n选定具体需要的内容\n其中v-slot可以简写为#\n 组件生命周期\n创建期,挂载期,更新期,销毁期\nbeforeCreate 组件创建之前\ncreated 组件创建之后\nbeforeMount 组件渲染之前\nmounted 组件渲染之后\nbeforeUpdate 组件更新之前\nupdated 组件更新之后\nbeforeUnmount 组件销毁之前\nunmounted 组件销毁之后\n 生命周期应用\n\n通过ref获取DOM结构\n\nmounted\n\n模拟网络请求渲染数据\n\ncreated,推荐mounted\n 动态组件\n<component :is="ComponentData"></component>\n 组件保持存活\n使用动态组件切换组件时,被切换的组件会被卸载。使用<keep-alive>强制使组件保持存活\n<keep-alive>\n\t<component :is="ComponentData"></component>\n</keep-alive>\n保持存活,不会被卸载 ,数据不会被重新初始化\n 异步组件\n<script>\n import defineAsyncComponent from 'vue'\n const ComponentB = defineAsyncComponent(()=>{\n import("/ComponentB.vue")\n })\n</script>\n 依赖注入\nprops逐级透传可以用provide和inject解决\n上级组件\nprovide{\n\tmsg: "123"\n}\n//或者\nprovide(){\n\treturn{\n message = this.message\n //获取data中数据\n\t}\n}\n子级组件\ninject:["msg"]\n即可获取msg内容{{msg}}\n只能由上到下,不能反向传递\n可以在main.js提供全局数据\napp.provide("global","123")\n Vue应用\n 应用实例\n每一个vue应用都是通过createApp函数创建\nimport {createApp} from 'vue'\nconst app = createApp({\n\t//根组件选项\n})\n一个Vue项目中,有且只有一个Vue实例对象\n 根组件\nimport App from './App.vue'\nconst app = createApp(App);\n 挂载应用\n应用实例调用了.mount()才能渲染出来,该方法接受一个容器参数\napp.mount('#app')\n<div id="app"></div>\n 公共资源\n位于.assets下存放公共资源,包括css文件,图片,字体等\n","categories":["前端"],"tags":[]},{"title":"算法基础数据结构","url":"/algorithm-data-struct/","content":" 链表与邻接表\n由于用结构体+指针比较慢,一般在面试题使用,在这里使用数组模拟链表\n\n单链表\n\ne[N]:储存链表结点的值\nne[N]:储存结点的下一个结点下标,其中空结点下标为-1\n// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点\nint head, e[N], ne[N], idx;\n\n// 初始化\nvoid init()\n{\n head = -1;\n idx = 0;\n}\n\n// 在链表头插入一个数a\nvoid insert(int a)\n{\n e[idx] = a, ne[idx] = head, head = idx ++ ;\n}\n\n//在a插到下标是k的结点后面\nvoid insert(int a,int k)\n{\n e[idx] = a, ne[idx] = ne[k], ne[k] = idx ++ ;\n}\n\n// 将头结点删除,需要保证头结点存在\nvoid remove()\n{\n head = ne[head];\n}\n\n// 将下标为k的结点的后一个点删除\nvoid remove(k)\n{\n ne[k] = ne[ne[k]];\n}\n\n双链表\n\n作用:优化某些问题\n// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点\nint e[N], l[N], r[N], idx;\n\n// 初始化\nvoid init()\n{\n //0是左端点,1是右端点\n r[0] = 1, l[1] = 0;\n idx = 2;\n}\n\n// 在节点a的右边插入一个数x\nvoid insert(int a, int x)\n{\n e[idx] = x;\n l[idx] = a, r[idx] = r[a];\n l[r[a]] = idx, r[a] = idx ++ ;\n}\n\n// 删除节点a\nvoid remove(int a)\n{\n l[r[a]] = l[a];\n r[l[a]] = r[a];\n}\n\n邻接表\n\nN个单链表,用于存储树和图\n 栈\n先进后出(FILO)\n// tt表示栈顶\nint stk[N], tt = 0;\n\n// 向栈顶插入一个数\nstk[ ++ tt] = x;\n\n// 从栈顶弹出一个数\ntt -- ;\n\n// 栈顶的值\nstk[tt];\n\n// 判断栈是否为空,如果 tt > 0,则表示不为空\nif (tt > 0)\n{\n\tnot empty\n}else\n{\n empty\n}\n 队列\n先进先出(FIFO)\n// hh 表示队头,tt表示队尾\nint q[N], hh = 0, tt = -1;\n\n// 向队尾插入一个数\nq[ ++ tt] = x;\n\n// 从队头弹出一个数\nhh ++ ;\n\n// 队头的值\nq[hh];\n\n// 判断队列是否为空,如果 hh <= tt,则表示不为空\nif (hh <= tt)\n{\n\n}\n循环队列\n// hh 表示队头,tt表示队尾的后一个位置\nint q[N], hh = 0, tt = 0;\n\n// 向队尾插入一个数\nq[tt ++ ] = x;\nif (tt == N) tt = 0;\n\n// 从队头弹出一个数\nhh ++ ;\nif (hh == N) hh = 0;\n\n// 队头的值\nq[hh];\n\n// 判断队列是否为空,如果hh != tt,则表示不为空\nif (hh != tt)\n{\n\n}\n 单调栈\n题型:求给定序列每一个数左/右边离他最近的比他大/小的数\nint tt = 0;\nfor (int i = 1; i <= n; i ++ )\n{\n while (tt && check(stk[tt], i)) tt -- ;\n stk[ ++ tt] = i;\n}\n 单调队列\n题型:求滑动窗口的最大/小值\nint hh = 0, tt = -1;\nfor (int i = 0; i < n; i ++ )\n{\n while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头是否滑出窗口\n while (hh <= tt && check(q[tt], i)) tt -- ;\n q[ ++ tt] = i;\n}\n KMP\n对于字符串sss,判断是否包含模式串ttt\n// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度\n//求模式串的Next数组:\nfor (int i = 2, j = 0; i <= m; i ++ )\n{\n while (j && p[i] != p[j + 1]) j = ne[j];\n if (p[i] == p[j + 1]) j ++ ;\n ne[i] = j;\n}\n\n// 匹配\nfor (int i = 1, j = 0; i <= n; i ++ )\n{\n while (j && s[i] != p[j + 1]) j = ne[j];\n if (s[i] == p[j + 1]) j ++ ;\n if (j == m)\n {\n j = ne[j];\n // 匹配成功后的逻辑\n }\n}\n Trie树\n快速存储和查找字符串集合的数据结构\nint son[N][26], cnt[N], idx;\n// 0号点既是根节点,又是空节点\n// son[][]存储树中每个节点的子节点\n// cnt[]存储以每个节点结尾的单词数量\n\n// 插入一个字符串\nvoid insert(char *str)\n{\n int p = 0;\n for (int i = 0; str[i]; i ++ )\n {\n int u = str[i] - 'a';\n if (!son[p][u]) son[p][u] = ++ idx;\n p = son[p][u];\n }\n cnt[p] ++ ;\n}\n\n// 查询字符串出现的次数\nint query(char *str)\n{\n int p = 0;\n for (int i = 0; str[i]; i ++ )\n {\n int u = str[i] - 'a';\n if (!son[p][u]) return 0;\n p = son[p][u];\n }\n return cnt[p];\n}\n最大异或对\n前缀统计\n 并查集\n\n将两个集合合并\n询问两个元素是否在一个集合当中\n\n近乎O(1)O(1)O(1)\n基本原理:每一个集合用一棵树表示,树根的编号就是整个集合的编号。每个节点存储它的父节点p[x]p[x]p[x]表示xxx的父节点\n\n如何判断是树根?\n\np[x]=xp[x] = xp[x]=x\n\n如何求xxx的集合编号\n\nwhile(p[x]!=x) x = p[x]\n\n如何合并两个区间\n\n设p[x]为x集合编号,p[y]是y集合编号。p[x]=y\n优化:路径压缩,先搜索一遍,再将节点的父节点直接指向树根\n(1)朴素并查集:\n\n int p[N]; //存储每个点的祖宗节点\n\n // 返回x的祖宗节点+路径压缩\n int find(int x)\n {\n if (p[x] != x) p[x] = find(p[x]);\n return p[x];\n }\n\n // 初始化,假定节点编号是1~n\n for (int i = 1; i <= n; i ++ ) p[i] = i;\n\n // 合并a和b所在的两个集合:\n p[find(a)] = find(b);\n\n\n(2)维护size的并查集:\n\n int p[N], size[N];\n //p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量\n\n // 返回x的祖宗节点\n int find(int x)\n {\n if (p[x] != x) p[x] = find(p[x]);\n return p[x];\n }\n\n // 初始化,假定节点编号是1~n\n for (int i = 1; i <= n; i ++ )\n {\n p[i] = i;\n size[i] = 1;\n }\n\n // 合并a和b所在的两个集合:\n size[find(b)] += size[find(a)];\n p[find(a)] = find(b);\n\n\n(3)维护到祖宗节点距离的并查集:\n\n int p[N], d[N];\n //p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离\n\n // 返回x的祖宗节点\n int find(int x)\n {\n if (p[x] != x)\n {\n int u = find(p[x]);\n d[x] += d[p[x]];\n p[x] = u;\n }\n return p[x];\n }\n\n // 初始化,假定节点编号是1~n\n for (int i = 1; i <= n; i ++ )\n {\n p[i] = i;\n d[i] = 0;\n }\n\n // 合并a和b所在的两个集合:\n p[find(a)] = find(b);\n d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量\n【模版】并查集\n食物链\n并查集题单\n 堆\n如何手写一个堆?\n\n插入一个数\n求集合中的最小值\n删除最小值\n删除任意一个元素\n修改任意一个元素\n\nSTL自带堆没有实现\n是一个完全二叉树\n小根堆:根小于等于左右孩子的值\n存储:一维数组,对于iii,左孩子2i2i2i,右孩子2i+12i+12i+1\n借助down(x)down(x)down(x) 与 up(x)up(x)up(x)函数实现上述五种操作\nheap[++size] = x;up(size);\nheap[1]\nheap[1] = heap[size];size--;down(1);\nheap[k] = heap[size];size--;down(k);up(k);\nheap[k] = x;down[k];up[k]\n\n// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1\n// ph[k]存储第k个插入的点在堆中的位置\n// hp[k]存储堆中下标是k的点是第几个插入的\nint h[N], ph[N], hp[N], size;\n\n// 交换两个点,及其映射关系\nvoid heap_swap(int a, int b)\n{\n swap(ph[hp[a]],ph[hp[b]]);\n swap(hp[a], hp[b]);\n swap(h[a], h[b]);\n}\n\nvoid down(int u)\n{\n int t = u;\n if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;\n if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;\n if (u != t)\n {\n heap_swap(u, t);\n down(t);\n }\n}\n\nvoid up(int u)\n{\n while (u / 2 && h[u] < h[u / 2])\n {\n heap_swap(u, u / 2);\n u >>= 1; // u/=2\n }\n}\n\n// O(n)建堆\nfor (int i = n / 2; i; i -- ) down(i);\n包含映射的堆不常用,在dijkstra算法中应用\n 哈希表\n 存储结构\nO(1)O(1)O(1)将大范围数映射到小范围值域(离散化是特殊的hash方式)\n\nxmod rangex\\mod\\ rangexmod range\n处理冲突\n\n\n拉链法\n\n创建一维数组存储所有哈希值,若不同元素对应同一个哈希值,则在该值拉链下来(链表)\nint h[N], e[N], ne[N], idx;\n\n // 向哈希表中插入一个数\n void insert(int x)\n {\n int k = (x % N + N) % N; //防止(x % N) 是负数\n e[idx] = x;\n ne[idx] = h[k];\n h[k] = idx ++ ;\n }\n\n // 在哈希表中查询某个数是否存在\n bool find(int x)\n {\n int k = (x % N + N) % N;\n for (int i = h[k]; i != -1; i = ne[i])\n if (e[i] == x)\n return true;\n\n return false;\n }\n\n开放寻址法\n\n只开辟一个一维数组,经验长度为题目范围的2-3倍\n若应该放入的位置已经有元素,则寻找下一个可以存入的位置\nint h[N];\n\n // 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置\n int find(int x)\n {\n int t = (x % N + N) % N;\n while (h[t] != null && h[t] != x)\n {\n t ++ ;\n if (t == N) t = 0; //从头查找\n }\n return t;\n }\n其中null=0x3f3f3f3fnull = 0x3f3f3f3fnull=0x3f3f3f3f\n 字符串哈希\n将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低\n小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果\ntypedef unsigned long long ULL;\nULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64\n\n// 初始化\np[0] = 1;\nfor (int i = 1; i <= n; i ++ )\n{\n h[i] = h[i - 1] * P + str[i]; //前面的值的指数阶都加一,所以要乘以P\n p[i] = p[i - 1] * P;\n}\n\n// 计算子串 str[l ~ r] 的哈希值\nULL get(int l, int r)\n{\n return h[r] - h[l - 1] * p[r - l + 1];\n}\n C++ STL\nvector, 变长数组,倍增的思想\n size() 返回元素个数\n empty() 返回是否为空\n clear() 清空\n front()/back()\n push_back()/pop_back()\n begin()/end()\n []\n 支持比较运算,按字典序\n\npair<int, int>\n first, 第一个元素\n second, 第二个元素\n 支持比较运算,以first为第一关键字,以second为第二关键字(字典序)\n\nstring,字符串\n size()/length() 返回字符串长度\n empty()\n clear()\n substr(起始下标,(子串长度)) 返回子串\n c_str() 返回字符串所在字符数组的起始地址\n\nqueue, 队列\n size()\n empty()\n push() 向队尾插入一个元素\n front() 返回队头元素\n back() 返回队尾元素\n pop() 弹出队头元素\n\npriority_queue, 优先队列,默认是大根堆\n size()\n empty()\n push() 插入一个元素\n top() 返回堆顶元素\n pop() 弹出堆顶元素\n 定义成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;\n\nstack, 栈\n size()\n empty()\n push() 向栈顶插入一个元素\n top() 返回栈顶元素\n pop() 弹出栈顶元素\n\ndeque, 双端队列\n size()\n empty()\n clear()\n front()/back()\n push_back()/pop_back()\n push_front()/pop_front()\n begin()/end()\n []\n\nset, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列\n size()\n empty()\n clear()\n begin()/end()\n ++, -- 返回前驱和后继,时间复杂度 O(logn)\n\n set/multiset\n insert() 插入一个数\n find() 查找一个数\n count() 返回某一个数的个数\n erase()\n (1) 输入是一个数x,删除所有x O(k + logn)\n (2) 输入一个迭代器,删除这个迭代器\n lower_bound()/upper_bound()\n lower_bound(x) 返回大于等于x的最小的数的迭代器\n upper_bound(x) 返回大于x的最小的数的迭代器\n map/multimap\n insert() 插入的数是一个pair\n erase() 输入的参数是pair或者迭代器\n find()\n [] 注意multimap不支持此操作。 时间复杂度是 O(logn)\n lower_bound()/upper_bound()\n\nunordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表\n 和上面类似,增删改查的时间复杂度是 O(1)\n 不支持 lower_bound()/upper_bound(), 迭代器的++,--\n\nbitset, 圧位\n bitset<10000> s;\n ~, &, |, ^\n >>, <<\n ==, !=\n []\n\n count() 返回有多少个1\n\n any() 判断是否至少有一个1\n none() 判断是否全为0\n\n set() 把所有位置成1\n set(k, v) 将第k位变成v\n reset() 把所有位变成0\n flip() 等价于~\n flip(k) 把第k位取反\n","categories":["算法"],"tags":[]},{"title":"2023年中闲聊","url":"/2023%E5%B9%B4%E4%B8%AD%E9%97%B2%E8%81%8A/","content":"现在是2023年6月30日\n环境工程的期末考陆陆续续已经考完了,秉承不挂就行的心态\n这学期参加的两个比赛(ACM校内选拔赛+ACM软件外包创新赛)都以失败告终\n还是自己太菜了\n暑假的计划是 考研数据结构 + ACwing算法基础课,准备混一些奖项牌子\n噶油!\n","categories":["闲聊"],"tags":[]},{"title":"算法基础课1-3(杂乱)","url":"/algorithm-1.3/","content":" 双指针算法\n\n两个序列,两个指针\n一个序列,两个指针\n\n结构\nfor(int i=0,j=0;i<n;i++){\n while(j<i && check(i,j)) j++;\n //每道题具体的逻辑\n}\n核心思想\n复杂度由O(n2)O(n^2)O(n2)优化到O(n)O(n)O(n)\n先想出朴素做法,寻找i与j之间的关系,是否有单调性,进行双指针优化\n 位运算\n\nn的二进制表示中第k位数字是几\n\n(k从个位开始算0,1,2…)\n\n先把第k位移到最后一位n>>k\n看个位是几 x&1\n\n\tn>>k&1\n\nlowbit(x)\n\n\t树状数组基本操作,返回x的最后一位1\n\tx&(-x)\n\t原理:-x=(~x+1)\n 离散化\n这里特指整数离散化\n将值域大,个数少的数组映射到0,1…n的自然数\n①数组中可能有重复元素,需要去重\n②如何求出xxx离散化后的值->二分\nC++模版\nvector<int> alls; // 存储所有待离散化的值\nsort(alls.begin(), alls.end()); // 将所有值排序\nalls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素\n\n// 二分求出x对应的离散化的值\nint find(int x) // 找到第一个大于等于x的位置\n{\n int l = 0, r = alls.size() - 1;\n while (l < r)\n {\n int mid = l + r >> 1;\n if (alls[mid] >= x) r = mid;\n else l = mid + 1;\n }\n return r + 1; // 映射到1, 2, ...n\n}\n由于Java中没有uniqueuniqueunique方法,其中排序与去重的具体实现如下\n// 去重 + 排序 \nList<Integer> distinctSorterAlls = alls.stream().distinct().sorted() .collect(Collectors.toList()); \n 区间合并\n\n\n按照区间左端点排序\n\n\n判断下一个区间与当前区间的关系\n\n相交\n\n更新右端点为两个区间的maxmaxmax\n\n\n不相交\n\n将当前区间更新为不相交的这个区间\n\n\n\n\n\n\tC++模版\n// 将所有存在交集的区间合并\nvoid merge(vector<PII> &segs)\n{\n vector<PII> res;\n\n sort(segs.begin(), segs.end());\n\n int st = -2e9, ed = -2e9;\n for (auto seg : segs)\n if (ed < seg.first)\n {\n if (st != -2e9) res.push_back({st, ed});\n st = seg.first, ed = seg.second;\n }\n else ed = max(ed, seg.second);\n\n if (st != -2e9) res.push_back({st, ed});\n\n segs = res;\n}\n","categories":["算法"],"tags":[]},{"title":"前缀和与差分算法","url":"/algorithm-prefix-sum-and-difference/","content":" 前缀和与差分\n一对逆运算\n 一维前缀和\n设有一列数据a1,a2,...,an−1,an{a}_1,{a}_2,...,{a}_{n-1},{a}_na1,a2,...,an−1,an\n定义Si=a1+a2+...+ai{S}_i=a_1+a_2+...+a_iSi=a1+a2+...+ai\n一般下标从1开始,S0=0S_0=0S0=0\nSiS_iSi的初始化: Si=Si−1+aiS_i = S_{i-1}+a_iSi=Si−1+ai\n作用\n快速地求出原数组中一段区间数的和\n对于区间[l,r][l,r][l,r]\n∑i=lrai=Sr−Sl−1\\sum_{i=l}^{r}a_i = S_r-S_{l-1}∑i=lrai=Sr−Sl−1\n 二维前缀和\n对于二维数组(矩阵)(a11a12...a1ja21a22...a2j............ai1ai2...aij)\\begin{pmatrix}\n a_{11}& a_{12} & ... & a_{1j}\\\\\n a_{21}& a_{22} & ... & a_{2j} \\\\\n ...& ... & ... & ...\\\\\n a_{i1}& a_{i2} & ... & a_{ij}\n\\end{pmatrix}⎝⎜⎜⎜⎛a11a21...ai1a12a22...ai2............a1ja2j...aij⎠⎟⎟⎟⎞\nSijS_{ij}Sij代表aija_{ij}aij左上角的所有元素和\n\n\n对于点(i,j)(i,j)(i,j),其二维前缀和SijS_{ij}Sij的初始化\nSij=Si−1,j+Si,j−1−Si−1,j−1+ai,jS_{ij}=S_{i-1,j}+S_{i,j-1}-S_{i-1,j-1}+a_{i,j}Sij=Si−1,j+Si,j−1−Si−1,j−1+ai,j\n\n\n设点(x1,y1)(x_1,y_1)(x1,y1)在(x2,y2)(x_2,y_2)(x2,y2)的左上角,则两点围成的矩形中所有元素和\nS=Sx2,y2−Sx2,y1−1−Sx1−1,y2+Sx1−1,y1−1S=S_{x_2,y_2}-S_{x_2,y_1-1}-S_{x_1-1,y_2}+S_{x_1-1,y_1-1}S=Sx2,y2−Sx2,y1−1−Sx1−1,y2+Sx1−1,y1−1\n\n\n 一维差分\n对一列数据a1,a2,a3,...,aia_1,a_2,a_3,...,a_ia1,a2,a3,...,ai\n构造b1,b2,b3,...,bib_1,b_2,b_3,...,b_ib1,b2,b3,...,bi使得ai=b1+b2+...+bia_i=b_1+b_2+...+b_iai=b1+b2+...+bi\n即aaa为bbb的前缀和,bbb就是aaa的差分\n{b1=a1b2=a2−a1b3=a3−a2......bn=an−an−1\\left\\{\\begin{matrix}\nb_1=a_1\\\\\nb_2=a_2-a_1\\\\\nb_3=a_3-a_2\\\\\n ......\\\\\nb_n=a_n-a_{n-1}\n\\end{matrix}\\right.⎩⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎧b1=a1b2=a2−a1b3=a3−a2......bn=an−an−1\n作用\n若要把a1,a2,a3,...,aia_1,a_2,a_3,...,a_ia1,a2,a3,...,ai中[l,r][l,r][l,r]区间的aaa加ccc\n只需要使bl+=c,br+1−=cb_l+=c,b_{r+1}-=cbl+=c,br+1−=c\n模版\nimport java.util.Scanner;\n\npublic class Diff {\n public static void main(String[] args) {\n\n Scanner scanner = new Scanner(System.in);\n\n // 给出n数组大小和k增加次数\n int n = scanner.nextInt();\n int k = scanner.nextInt();\n\n // 搭建数组\n int[] arr = new int[n+1];\n int[] brr = new int[n+1];\n\n // 为arr赋值\n for (int i = 1; i < n+1; i++) {\n arr[i] = scanner.nextInt();\n }\n\n // 为brr赋值\n for (int i = 1; i < n+1; i++){\n brr[i] = arr[i] - arr[i-1];\n }\n\n while (k-- > 0){\n // 我们为arr的[l,r]区间加上c\n int l = scanner.nextInt();\n int r = scanner.nextInt();\n int c = scanner.nextInt();\n\n brr[l] += c;\n brr[r+1] -= c;\n }\n\n // 计算输出结果即可(这里输出的需要是由b累计出来的a)\n // 也可以使用注释代码,最后输出arr即可\n for (int i = 1; i < n+1; i++) {\n brr[i] += brr[i-1];\n //arr[i] = brr[i]+arr[i-1];\n }\n\n // 最后输出结果\n for (int i = 1; i < n+1; i++) {\n System.out.println(brr[i]);\n }\n\n }\n}\n 二维差分\n原矩阵aija_{ij}aij,差分矩阵bijb_{ij}bij\nbx1,y1+=cb_{x1,y1}+=cbx1,y1+=c\nbx2+1,y−=cb_{x2+1,y}-=cbx2+1,y−=c\nbx1,y2+1−=cb_{x1,y2+1}-=cbx1,y2+1−=c\nbx2+1,y2+1+=cb_{x2+1,y2+1}+=cbx2+1,y2+1+=c\n","categories":["算法"],"tags":[]},{"title":"高精度算法","url":"/algorithm-high-precision/","content":"JavaJavaJava中使用BigIntegerBigIntegerBigInteger和BigDecimalBigDecimalBigDecimal类实现\nC++C++C++模版\n\n高精度加法\n\n// C = A + B, A >= 0, B >= 0\nvector<int> add(vector<int> &A, vector<int> &B)\n{\n if (A.size() < B.size()) return add(B, A);\n\n vector<int> C;\n int t = 0;\n for (int i = 0; i < A.size(); i ++ )\n {\n t += A[i];\n if (i < B.size()) t += B[i];\n C.push_back(t % 10);\n t /= 10;\n }\n\n if (t) C.push_back(t);\n return C;\n}\n\n高精度减法\n\n// C = A - B, 满足A >= B, A >= 0, B >= 0\nvector<int> sub(vector<int> &A, vector<int> &B)\n{\n vector<int> C;\n for (int i = 0, t = 0; i < A.size(); i ++ )\n {\n t = A[i] - t;\n if (i < B.size()) t -= B[i];\n C.push_back((t + 10) % 10);\n if (t < 0) t = 1;\n else t = 0;\n }\n\n while (C.size() > 1 && C.back() == 0) C.pop_back();\n return C;\n}\n\n高精度乘低精度\n\n// C = A * b, A >= 0, b >= 0\nvector<int> mul(vector<int> &A, int b)\n{\n vector<int> C;\n\n int t = 0;\n for (int i = 0; i < A.size() || t; i ++ )\n {\n if (i < A.size()) t += A[i] * b;\n C.push_back(t % 10);\n t /= 10;\n }\n\n while (C.size() > 1 && C.back() == 0) C.pop_back();\n\n return C;\n}\n\n高精度除以低精度\n\n// A / b = C ... r, A >= 0, b > 0\nvector<int> div(vector<int> &A, int b, int &r)\n{\n vector<int> C;\n r = 0;\n for (int i = A.size() - 1; i >= 0; i -- )\n {\n r = r * 10 + A[i];\n C.push_back(r / b);\n r %= b;\n }\n reverse(C.begin(), C.end());\n while (C.size() > 1 && C.back() == 0) C.pop_back();\n return C;\n}\n\n高精度乘以高精度\n\ncin>>a1>>b1;\n int lena=strlen(a1);\nint lenb=strlen(b1);\n for(i=1;i<=lena;i++)a[i]=a1[lena-i]-'0';\n for(i=1;i<=lenb;i++)b[i]=b1[lenb-i]-'0';\nfor(i=1;i<=lenb;i++)\n\tfor(j=1;j<=lena;j++)\n\t\tc[i+j-1]+=a[j]*b[i];\n for(i=1;i<lena+lenb;i++)\n\tif(c[i]>9)\n\t{\n\t\tc[i+1]+=c[i]/10;\n\t\tc[i]%=10;\n\t}\nlen=lena+lenb;\n while(c[len]==0&&len>1)len--;\nA+B Problem(高精)\nA*B Problem(高精)\n","categories":["算法"],"tags":[]},{"title":"排序算法","url":"/algorithm-sort/","content":" 快速排序\n分治思想,时间复杂度$O(nlogn)-O(n^2) $\n期望时间复杂度O(nlogn)O(nlogn)O(nlogn)\n\n\n数组中找一个值xxx作为分界点(可以是arr[l]arr\\left [ l \\right ]arr[l] ,arr[r]arr\\left [ r \\right ]arr[r],arr[l+r2]arr\\left [ \\frac{l+r}{2} \\right ]arr[2l+r] 等等…)\n\n\n调整区间,使得左边的区间所有数≤\\le≤x,右边区间所有数>>>x\n\n定义两个指针分别在左右边界\niii不断右移,直到遇到arr[i]arr[i]arr[i] >x>x>x,就停下\njjj不断左移,直到遇到arr[j]≤xarr[j]\\le xarr[j]≤x,就停下\n交换arr[i]arr[i]arr[i]与arr[j]arr[j]arr[j]\n\n\n\n递归处理左右区间\n\n\n模版\npublic static void quick_sort(int q[], int l, int r){\n if (l >= r) return;\n int i = l - 1, j = r + 1, x = q[l + r >> 1];\n while (i < j){\n do i ++ ; while (q[i] < x);\n do j -- ; while (q[j] > x);\n if (i < j) {\n int t = q[i];\n q[i] = q[j];\n q[j] = t; \n }\n }\n quick_sort(q, l, j);\n quick_sort(q, j + 1, r);\n}\n 归并排序\n分治思想,O(nlogn)O(nlogn)O(nlogn)\n\n\n确定分界点 mid=l+r2mid = \\frac{l+r}{2}mid=2l+r\n\n\n递归排序leftleftleft和rightrightright\n\n\n归并:合二为一\n\n双指针指向leftleftleft和rightrightright的第一个元素\n创建一个空数组resresres存放结果\n指针比较,如果left[i]<right[j]left[i]<right[j]left[i]<right[j],则把left[i]left[i]left[i]放入resresres,iii向后移动一位,继续比较\n如果left[i]=right[j]left[i]=right[j]left[i]=right[j],则把left[i]left[i]left[i]放入resresres,以维持稳定\n\n\n\n模版\npublic static void merge_sort(int q[], int l, int r){\n if (l >= r) return;\n int mid = l + r >> 1;\n\n merge_sort(q, l, mid);\n merge_sort(q, mid + 1, r);\n\n int k = 0, i = l, j = mid + 1;\n\n while (i <= mid && j <= r)\n if (q[i] < q[j]) tmp[k ++ ] = q[i ++ ];\n else tmp[k ++ ] = q[j ++ ];\n\n while (i <= mid) tmp[k ++ ] = q[i ++ ];\n while (j <= r) tmp[k ++ ] = q[j ++ ];\n\n for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];\n}\n","categories":["算法"],"tags":[]},{"title":"二分查找与二分答案算法","url":"/algorithm-two-divided/","content":" 整数二分\n提示信息\n\n题目保证有解\n单调性\n求最大值的最小化\n\n思路\n对于区间[l,r][l,r][l,r],其中一部分满足条件check(x)=truecheck(x)=truecheck(x)=true,另一部分不满足\n\n对于寻找不满足区间的边界\n\nmid=l+r+12mid = \\frac{l+r+1}{2}mid=2l+r+1\n若check(mid)=truecheck(mid)=truecheck(mid)=true 则说明边界值在[mid,r][mid,r][mid,r]\n更新语句为l=midl = midl=mid\n若check(mid)=falsecheck(mid)=falsecheck(mid)=false 则说明边界值在[l,mid−1][l,mid-1][l,mid−1]\n更新语句为r=mid−1r = mid-1r=mid−1\n\n对于寻找满足区间的边界\n\nmid=l+r2mid = \\frac{l+r}{2}mid=2l+r\n若check(mid)=truecheck(mid)=truecheck(mid)=true 则说明边界值在[l,mid][l,mid][l,mid]\n更新语句为r=midr = midr=mid\n若check(mid)=falsecheck(mid)=falsecheck(mid)=false 则说明边界值在[mid+1,r][mid+1,r][mid+1,r]\n更新语句为l=mid+1l=mid+1l=mid+1\n模版\npublic static boolean check(int x) {/* ... */} // 检查x是否满足某种性质\n\n// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:\npublic static int bsearch_1(int l, int r)\n{\n while (l < r)\n {\n int mid = l + r >> 1;\n if (check(mid)) r = mid; // check()判断mid是否满足性质\n else l = mid + 1;\n }\n return l;\n}\n// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:\npublic static int bsearch_2(int l, int r)\n{\n while (l < r)\n {\n int mid = l + r + 1 >> 1;\n if (check(mid)) l = mid;\n else r = mid - 1;\n }\n return l;\n}\n如果l=midl=midl=mid ,最开始的midmidmid就要补上+1+1+1\n题目参考\n冶炼金属\n垦天计划\n 浮点数二分\npublic static boolean check(double x) {/* ... */} // 检查x是否满足某种性质\n\ndouble bsearch_3(double l, double r)\n{\n final double eps = 1e-6; \n // eps 表示精度,取决于题目对精度的要求\n //比需要保留的位数多2\n while (r - l > eps)\n {\n double mid = (l + r) / 2;\n if (check(mid)) r = mid;\n else l = mid;\n }\n return l;\n}\n精度比需要保留的位数多-2次方\n可以把whilewhilewhile循环直接换成for100次\n","categories":["算法"],"tags":[]},{"title":"HelloWorld","url":"/HelloWorld/","content":"把原来的一些杂乱的学习笔记都删了\n因为我觉得博客应该是记录一些自己的想法,而不是生抄别人的博客或者代码\n\n测试一下图片\npublic class Main{\n public static void init(){\n return;\n }\n}\n测试一下代码块\n咕咕咕\n","categories":["闲聊"],"tags":[]}]
\ No newline at end of file
diff --git a/tags/index.html b/tags/index.html
index 1f39f0f..fef1189 100644
--- a/tags/index.html
+++ b/tags/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/tools/index.html b/tools/index.html
index 100ea2b..5bd0a33 100644
--- a/tools/index.html
+++ b/tools/index.html
@@ -399,16 +399,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1
diff --git a/vue/index.html b/vue/index.html
index 603ce48..feeac32 100644
--- a/vue/index.html
+++ b/vue/index.html
@@ -433,16 +433,16 @@
-
+
- 前端
+ 论文
1
-
+
- 论文
+ 前端
1