一.位运算
一般基础的状态压缩就是将一行的状态压成一个数,这个数的二进制形式反映了这一行的情况。由于使用二进制来保存被压缩的状态,所以要用到神奇的二进制位运算操作,将一个十进制数转成二进制进行位运算操作再转回十进制数
总结
1.计算机有一套机制用二进制表示(正/负)整数/小数
2.平时我们写代码不用刻意写 ‘<<’ 或者 ‘>>’ 等移位运算符,因为编译器会自动做优化
如果要写,建议加括号
3.熟记真值表
4.XOR有很多性质,最重要的是:定义本身和交换律,结合律
其他性质可从定义和交换律,结合律推导出来
5.XOR的性质产生了很多等式,这些等式有时候可以写成状态转移方差,可以把原问题转换成dp问题
6.bit部分考的很少很少,甚至低于dp的范畴,主要用于状态压缩或者暴力解的时候方便穷尽所有的case,在contest里面常见
二.Reservoir Sampling / Rejection Sampling
水库抽样
public int getRandom() {
int res = -1, count = 1;
for (ListNode cur = head; cur != null; cur = cur.next)
if (found_a_valid_case)
if (random.nextInt(count++) == 0) res = cur.val;
return res;
}
三.离线算法
什么叫在线算法?就是依次处理每一个query,对每一个query的计算,和之后的query无关,也不会用到之后的query信息(但可能也可以使用之前的query信息)
所以,在线算法,可以用来处理数据流。算法不需要一次性把所有的query都收集到再处理。大家也可以想象成:把这个算法直接部署到线上,尽管在线上可能又产生了很多新的query,也不影响,算法照常运行
离线算法则不同。离线算法需要把所有的信息都收集到,才能运行。处理当前query的计算过程,可能需要使用之后query的信息
总结
1.离线算法可以优化时间,缺点是需要知道所有的data,不能及时给每一个query答案,需要后台bash处理
2.这样做的核心在于,我们一边处理query,一边处理边,不是一次性的把所有的边都考虑进来,而是根据query的limit从小到大的限制,从小到大依次考虑边。注意,这个思路需要我们首先对整个query数组排序,之后再处理,所以需要收集到所有query信息以后再执行,它是一个离线算法
3.在线算法也可以实现类似的时间复杂度但是需要一些特殊算法,如树上倍增 + LCA
for (int i = 0, j = 0; i < N; i++) {
int[] query = queries[i];
while (j < M && edgeList[j][2] < queries[i][2]) //如果构造edge小于limit就创建
dsu.union(edgeList[j][0],edgeList[j++][1]);
res[queries[i][3]] = dsu.find(queries[i][0]) == dsu.find(queries[i][1]);
}
四.滚动哈希
考的不多,多为Hard题
public int search(String S, int L, int[] nums) { //sliding window size = L
long h = 0, aL = 1;
for (int i = 0; i <L; i++) h = (h * a + nums[i]) % MOD;
for (int i = 1; i <= L; i++) aL = (aL * a) % MOD;
Set<Long> seen = new HashSet(List.of(h));
for (int start = 1; start < n - L + 1; start++) {
h = h * a; //move window
h = (h - nums[start - 1] * aL % MOD + MOD) % MOD; //remove last digit
h = (h + nums[start + L - 1]) % MOD; //input new digit
if (!seen.add(h)) return start;
}
return -1;
}