训练记录
写一点【】之前复习的东西,因为半年没学 OI 了,所以可能有一堆基础的东西。
6.21
- 归并排序。
递归的思想,每次把当前的区间分成两段,分别排序再进行合并。
由于最后分成的区间一定只有一个数,所以只需要考虑如何合并。
对于两段有序的区间 \(a,b\)(\(a\) 表示左区间,\(b\) 表示右区间),维护两个指针 \(i,j\),每次比较 \(a_i\) 和 \(b_j\),然后把较小数插进新的序列中,再更新指针。
这样合并也做完了,复杂度是 \(O(n\log n)\) 的。
- 逆序对
一个归并排序的小性质。
令 \(a\) 表示左区间 \([l,mid]\),\(b\) 表示右区间 \([mid+1,r]\),
在进行合并的过程中,由于划分区间是按照位置划分的,所以如果 \(a_i>b_j\) 的话,那说明 \(i,j\) 是一对逆序对。因为 \(a,b\) 都是有序的,所以 \(a_{mid}>a_{mid-1}>...>a_{i+1}>a_i>b_j\)
于是当前所产生的答案数就是 \(mid-i+1\)。
统计答案即可。
7.2
- 最小生成树
kruskal 求最小生成树。
按照边权排序,然后从小到大加边,如果新加的边不会与已经加完的点形成一个环就把这条边加进去,直到加满 \(n-1\) 条边。
按照题目建图,注意 \(K_{i,j}=0\) 时边权为 \(A\)。
然后跑 kruskal,答案还要加上 \(A\),因为最开始要原价购买一件物品。
坑点:答案注意和 \(A\times B\) 取 min,因为打折也可以打十一折。
建图同上。
发现我们并不知道要在哪个点挖水井,所以不能像上题一样直接加。
这里我们建立一个超级源点,将这个点和所有的点 \(i\) 连一条价值为 \(w_i\) 的边,然后跑 kruskal 即可。
- 染色法判定二分图
判断一个图是否为二分图就是判断图中有没有奇数环。
设这个奇数环的点数为 \(n\),那么前 \(n-1\) 个点可以被分成两个点集,使得这两个点集内部没有连边。
但是最后的第 \(n\) 个点与这两个点集的点都有连边,所以有奇数环的图不是二分图。
判断是否有奇数环可以用染色法,这个过程用 dfs 实现,然后搜到两个相邻的点是同色的就说明有奇数环。
- 字符串哈希
对于一个字符串的每个前缀都预处理出一个哈希值,这个哈希值是一个 \(P\) 进制数。把字母映射成数字,然后转 \(P\) 进制。
这样就可以求区间 \([L,R]\) 的哈希值。
令 \(h[i]\) 为 \([1,i]\) 的前缀哈希值。
考虑前缀和,所以 \(h[L-1]\) 和 \(h[R]\) 是重要的。
但是因为这两个前缀哈希值的位数不相等,所以不能直接相减来求,于是考虑把 \(h[L-1]\) 后面补上所差的位数。
默认左边的是高位数。
考虑 \(h[L-1]\),因为第 \(L-1\) 位是 \(p^0\),所以第 \(1\) 位是 \(p^{L-2}\)。
考虑 \(h[R]\),因为第 \(R\) 位是 \(p^0\),所以第 \(1\) 位是 \(p^{R-1}\)。
所以这两个值相差 \(R-1-(l-2)=R-L+1\) 位。
于是 \([L,R]\) 的哈希值为 \(h[R]-h[L-1]*p[R-L+1]\)
\(p[i]\) 表示 \(P^i\)。\(p[0]=1\)。
枚举 \(n\) 的约数,然后哈希暴力 \(check\),然后答案是 \(n/len\)。
7.3
- 二分图最大匹配
分别考虑每一个左部点。遍历每一个和当前左部点有连边的右部点,如果这个右部点没有被匹配或者能够让现在和这个右部点匹配的左部点找到新的右部点匹配,则把这个右部点和当前左部点匹配。
- LCA
首先 \(fa[u][k]\) 表示从点 \(u\) 向上走 \(2^k\) 步能够到达的点。
特别的,\(fa[u][0]\) 为点 \(u\) 的父节点。
不难得到转移方程 \(fa[u][k]=fa[fa[u][k-1]][k-1]\)。
找点 \(a,b\) 的 LCA,令点 \(a\) 是深度较大的点。
先让点 \(a\) 跳到和点 \(b\) 同一深度的点上。
从大到小枚举 \(k\),这样通过二进制拼凑就一定能跳到同一层。
然后两个点同时跳,知道跳到同一个点上,这个点就是 LCA。
深度和 fa 数组都可以通过 dfs/bfs 预处理。
7.4
- st 表
考虑倍增,\(st[i][j]\) 表示从 \(i\) 开始的 \(2^j\) 个数的最大/最小值。
转移和 LCA 类似,从 \(j-1\) 转移。
\(st[i][0]=a[i]\)。\(a\) 是原来的数组。
这里转移的时候要先枚举 \(j\) ,因为本质上是一个大区间由两个小区间合并得到,先枚举区间长度 \(j\) 就可以保证在转移大区间的时候小区间已经被算过了。区间 dp 也是同样的道理。
考虑查询,找到最大的 \(k\) 使得 \(2^k \le len\)。\(len\) 是区间长度。
所以 \(2 \times 2^k \ge len\),也就是说两段长度为 \(2^k\) 的区间就能把询问的区间 \([l,r]\) 覆盖掉。
所以这两段区间分别是 \([l,l+2^k-1]\) 和 \([r-2^k+1,r]\)。
再把这两段区间的答案取 max 即可。
- 二分
二分答案,贪心 check,复杂度是 \(O(n \log n)\) 的。
浮点二分。
二分平均值,把每个数减去 \(mid\)。问题转化为了是否由长度大于等于 \(L\) 的区间和非负。
区间和可以用前缀和维护。
直接枚举区间是平方级别的,所以考虑优化。
先枚举右端点 \(r\)。
当枚举到 \(i\) 时:
\(l \in [1,i-L+1]\)。
要使区间和最大,就是让 \(s[r]-s[l-1]\) 最大,也就是让 \(s[r]\) 尽可能大, \(s[l-1]\) 尽可能小。
于是我们在枚举的过程中记录 \(s[l-1]\) 的最小值,每次更新答案。
也就是记录 \(s[r-L+1-1=r-L]\) 的最小值即可。
所以 check 的复杂度是 \(O(n)\) 的。
由于输出要向下取整,所以要输出二分的 \(r\),因为 \(l \le mid \le r\),而浮点数二分的精度限制使得 \(l,mid,r\) 的整数部分最多差 \(1\),而这个时候 \(l,mid,r\) 显然都更接近 \(r\) 的整数部分,所以要输出 \(r\)。