模板——杂项
模板——杂项
KMP,SG 函数,二进制相关,数位 DP,对抗搜索,神秘康托展开,cout
输出流控制,三分(整数三分,实数三分),upper_bound 相关,CDQ
分治,莫队,sort + unique 离散化。
KMP
kmp[i] 即为 border。长度减去 border 为循环节。
1 |
|
SG 函数
把一堆石子分成多堆石子:子游戏取异或和。
一堆石子被取走一些石子:前驱状态取 mex。
1 |
|
二进制相关
- 用数表示集合。数的二进制的第 \(i\) 位的 01 状态可表示一个元素是否在集合中。
- \(\operatorname{lowbit}(x)\) 表示数
\(x\) 的最低的为 \(1\)
的一位。
lowbit(x)=x&(-x)。 - 判断 \(x\) 是不是 \(y\) 的子集:
x&y==x。 - \(x\) 和 \(y\) 的交集
x&y,并集x|y。 - \(x\) 是 \(y\) 的子集时,\(y-x\):
x^y。 - 枚举 \(S\)
的子集:
for(int i=S;i;i=i-1&S),其中 \(i\) 是 \(S\) 的子集,复杂度是 \(O(2^k)\)。枚举(全集的)子集的子集的复杂度为 \(O(3^n)\)。 - 判断 \(s\) 从右边数第 \(i\) 位是否为 \(1\):
s&(1<<(i-1))。 - 把 \(s\) 从右边数第 \(i\) 位变成 \(1\):
s|(1<<(i-1))。 - 位运算运算律:
- \((a \mid b) \mathbin{\&} c = (a \mathbin{\&} c) \mid (b \mathbin{\&} c),(a \mathbin{\&} b) \mid c = (a \mid c) \mathbin{\&} (b \mid c)\)(与或互相分配)
- \((a \oplus b) \mathbin{\&} c = (a \mathbin{\&} c) \oplus (b \mathbin{\&} c)\)
- \(\sim(a \mathbin{\&} b) = (\sim a) \mid (\sim b),\sim(a \mid b) = (\sim a) \mathbin{\&} (\sim b)\)(德摩根律)
- \(a \mathbin{\&} (a \mid b) = a,a \mid (a \mathbin{\&} b) = a\)(吸收律)
- \(a + b = a \oplus b + 2(a \mathbin{\&} b)\) 这本质上是把加法拆成“无进位和(XOR)”与“进位(AND 并左移)”两部分。
数位 DP
合法号码是不含前导零的 \(11\) 位的数字,要出现至少 \(3\) 个相邻的相同数字;号码中不能同时出现 \(8\) 和 \(4\)。
给定 \(l\) 和 \(r\)(不含前导零, \(11\) 位),求 \([l,r]\) 区间的合法号码数量。
1 |
|
对抗搜索
一个 \(n\times n\)(\(n\ge 2\))棋盘上有黑白棋子各一枚。游戏者 A 和 B 轮流移动棋子,A 先走。
- A 的移动规则:只能移动白棋子。可以往上下左右四个方向之一移动一格。
- B 的移动规则:只能移动黑棋子。可以往上下左右四个方向之一移动一格或者两格。
和通常的“吃子”规则一样,当某游戏者把自己的棋子移动到对方棋子所在的格子时,他就赢了。
两个游戏者都很聪明,当可以获胜时会尽快获胜,只能输掉的时候会尽量拖延时间。告诉你 \(n\) 和黑白棋子的坐标,你的任务是判断谁会赢,需要多少回合。
1 |
|
神秘康托展开
我们的目标是把全排列转化成一个变进制数,以方便我们进行加法。对于第 \(i\) 根手指,它有 \(n−i+1\) 种选择,根据位值原理,要想让每个数对应一个全排列,就要让这一位数是 \(n−i+1\) 进制的。
那么,整个过程分为三步:
- 将火星数变成变进制数;
- 将变进制数加上 \(m\);
- 将变进制数变成火星数。
我们来看一个实例: 将 \(1,4,5,2,3\) 变成变进制数:
- 首位 1 是 5 种选择 \(\{1,2,3,4,5\}\) 的第 1 种,故变为 0(从0开始)
- 次位 4 是 4 种选择 \(\{2,3,4,5\}\) 的第 3 种,故变为 2
- …
- 最后,排列 \(1,4,5,2,3\) 变成了 \((02200)\)
接下来给它加上 3 变成 \((02203)\),并处理进位:
- 末位是 1 进制的,进 3 得 \((02230)\)。
- 次低位是 2 进制的,满 2 进一得 \((02310)\)。
- 中间位是 3 进制的,满 3 进一得 \((03010)\)。
- 次位是 4 进制的,\(3<4\),不进位,得 \((03010)\)。
最后将 \((03010)\) 变回火星数。
- 首位 0 表示这位应选择 \(\{1,2,3,4,5\}\) 的第 1 种,即 1
- 次位 3 表示这位应选择 \(\{2,3,4,5\}\) 的第 4 种(1 被选过了),即 5
- …
所以本题答案为 14523 +3= 15243。
cout 输出流控制
1 | int n = 141; |
三分
三分用于单峰函数(例如二次函数)求最值等问题。
实数三分
求 calc 凹函数的最小值。
1 |
|
整数三分
1 | ll l, r;//凸函数极大值 |
upper_bound 相关
对于元素单调上升的数列:
lower_bound:返回第一个大于等于给定数的位置。upper_bound:返回第一个大于给定数的位置。
1 | //从小到大排列,找第一个>= |
1 | // 第一个 >= x 的索引,没有返回 -1 |
CDQ 分治
CDQ 分治是一种思想,依原理与写法的不同,大致分为三类:
- 解决和点对有关的问题。
- 1D 动态规划的优化与转移。
- 通过 CDQ 分治,将一些动态问题转化为静态问题。
点对
CDQ 分治解决这类问题的算法流程如下:
找到这个序列的中点 \(mid\),将 \((1,n)\) 这个序列拆成两个序列 \((1,mid)\) 和 \((mid+1,n)\),递归地处理包含于这两个区间的点对,设法处理跨越 \(mid\) 的点对。此时,跨越 \(mid\) 的点对相当于消除了一个限制条件,降低维护难度。
例如三维偏序,用分治消除一种偏序,再用树状数组处理二维偏序。
1 | void CDQ(ll l, ll r) { |
动态规划
1D/1D 动态规划指的是一类特定的 DP 问题,该类题目的特征是 DP 数组是一维的,转移是 \(O(n)\) 的。如果条件良好的话,有时可以通过 CDQ 分治来把它们的时间复杂度由 \(O(n^2)\) 降至 \(O(n\log^2n)\)。
例如转移方程:\(dp_{i}=1+ \max_{j=1}^{i-1}dp_{j}[a_{j} < a_{i}][b_{j} < b_{i}]\)。
我们发现 \(dp_{j}\) 转移到 \(dp_{i}\) 这种转移关系也是一种点对间的关系,所以我们用类似 CDQ 分治处理点对关系的方式来处理它(转移也是需要先后顺序,也相当于偏序关系,变为类三维偏序问题)。
假设现在正在处理的区间是 \((l,r)\),算法流程大致如下:
- 如果 \(l=r\),说明 \(dp_{r}\) 值的 \(\max\) 部分已经被计算好了,直接令 \(dp_{r} \gets dp_{r} + 1\) 然后返回即可;
- 递归使用
solve(l,mid);处理所有 \(l \leq j \leq mid\),\(mid+1 \leq i \leq r\) 的转移关系;递归使用solve(mid+1,r)。
动态转静态
它适用于一些「需要支持做 xxx 修改然后做 xxx 询问」的数据结构题。该类题目有两个特点:
- 如果把询问离线,所有操作会按照时间自然地排成一个序列。
- 每一个修改均与之后的询问操作息息相关。而这样的「修改 - 询问」关系一共会有 \(O(n^2)\) 对。
例如:修改颜色,查询区间有几种颜色。
1 |
|
莫队
普通莫队
对于序列上的区间询问问题(序列长度 \(n\),询问次数 \(m\)),如果从 \([l,r]\) 的答案能够 \(O(1)\) 扩展到相邻的区间的答案,那么可以在 \(O(n\sqrt{m})\) 的复杂度内求出所有询问的答案。块长 \(S\) 取 \(\displaystyle \frac{n}{\sqrt{m}}\) 是最优的。
1 |
|
带修莫队
在原本 struct Query 的基础上,记录时间
t。询问和修改分开,移动当前询问区间后,再把修改的时刻暴力移动到当前询问区间的时刻。\(m\) 次询问,\(t\) 次修改,块长取 \(\dfrac{n^{2/3}t^{1/3}}{m^{1/3}}\)
时最优时间复杂度 \(O\left(n^{2/3}m^{2/3}t^{1/3}\right)\)(同阶时为
\(O\left(n^{5/3}\right)\)),实际操作中还是推荐设定
\(n^{2/3}\) 为块长。
1 |
|
回滚莫队
有时候 add 与 del
其一不好实现(例如查询区间内相同颜色的点的距离最大值不容易
del),采取块内右端点不断拓展,左端点每次拓展后暴力回滚的方法。
1 |
|
sort + unique 离散化
1 | // 离散化:sort + unique |