树上差分

首先,树上差分是从子节点推到根节点,定义 dd 差分数组

若修改 (x,y)(x,y) 路径上的点权,则 dx,dy+=k,dlca,dfalca=kd_x, d_y \mathrel{+}= k, d_{\mathrm{lca}}, d_{\mathrm{fa}_{\mathrm{lca}}} \mathrel{-}= k

1
2
3
4
5
6
void dfs(int u){
val[u] = d[u];
for (auto v : g[u]){
dfs(v), val[u] += val[v];
}
}

若修改 (x,y)(x,y) 路径上的边权,则 dx,dy+=k,dlca=2kd_x, d_y \mathrel{+}= k, d_{\mathrm{lca}}\mathrel{-}= 2k

1
2
3
4
5
6
7
8
int dfs(int u){
int ret = d[u];
for (auto i : g[u]){
i.w = dfs(i.v);
ret += i.w;
}
return ret;
}

P3128 最大流

阅读更多

求 LCA 的 Tarjan 算法

这是一个复杂度为 O(n+q)O(n+q) 的离线求 LCA 算法

用 dfs 进行遍历并打 tag

假设遍历到 uu,则已回溯的节点标为 22,遍历到但未回溯的节点(即 uuuu 的祖先)标 11,其余为 00

现在假设 uu 的子树处理完毕,考虑 uu 的处理

询问节点为 (u,v)(u, v)

此时,若 tagv=2\mathrm{tag}_v = 2,则可以保证 vv 向根走遇到的第一个 tag=1\mathrm{tag} = 1 的节点即为 LCA(u,v)\mathrm{LCA}(u,v),可以用反证法证明

若向根走遇到的第一个 tag=1\mathrm{tag} = 1 的节点,记为 wwwLCA(u,v)w \not = \mathrm{LCA}(u,v)

LCA(u,v)\mathrm{LCA}(u,v)pp

显然 ppuu 到根的路径上

ppww 下方,则 tagp1\mathrm{tag}_p \not= 1,但是 pp 又为 uu 的祖先,tag=1\mathrm{tag}=1

矛盾

ppww 上方,根据算法可得 wwvv 的 祖先,又 tagw=1\mathrm{tag}_w = 1wwuu 的祖先,所以 ww 必为 uuvv 的公共祖先,但是 ppww 的祖先

矛盾

证毕

下面的程序复杂度应该没有问题,但是要卡一下常,吸一口氧才能过

阅读更多

Miller Rabin 判素

数学

费马小定理

对于素数 pp 必存在

ap11(modp) a^{p-1} \equiv 1 \pmod{p}

所以可以随机选取 a[2,n1]a\in[2,n-1] 检验是否满足上式,若不满足,则必然是合数

遗憾的是,存在无穷多个 nn,可以满足

 x[2,n1]xn11(modn) \forall\ x\in[2,n-1]\\ x^{n-1}\equiv 1 \pmod{n}

也就是说使费马判素失效

二次探测定理

素数 pp 必满足方程

x21(modp) x^2\equiv1\pmod{p}

有且仅有两个解

x11, x2p1(modp) x_1\equiv1,\ x_2\equiv p-1\pmod{p}

实现

n1n-1 分解成 d2td\cdot2^t 使 tt 尽量大

然后 xbased(modn)x\equiv base^{d} \pmod{n}

然后不断进行二次探测

即如果满足 x≢1x\not\equiv 1x≢n1x\not\equiv n-1x21x^2\equiv 1 那么这个数解不是素数了

最后进行费马判素

1
2
3
4
5
6
7
8
9
10
11
12
13
bool miller_rabin(ll n) {
if (n <= 2) return n == 2;
int d = n - 1, t = 0;
while (d % 2 == 0) d /= 2, ++t;
rep(i, 0, T) { // T 是测试次数, p 是测试基
ll x = ksm(p[i] % (n - 2) + 2, d, n);
if (x == 1 || x == n - 1) continue; // 无效的二次探测
for (int j = 0; j < t; ++j, x = x * x % n)
if (x != n - 1 && x != 1 && x * x % n == 1) return 0;
if (x != 1) return 0; // 费马判素
}
return 1;
}

这是测试基选择的推荐

数据范围 测试基
2322^{32} 2,7,61
2642^{64} 2,3,5,7,11,13,17,19,23,29,31,37

粒子群算法

概述

这是一种随机算法

模仿鸟类捕食

在定义域中随机撒 nn 个点,需要维护(以二维定义域为例)

  • 坐标 p(x,y)p(x,y)
  • 速度 vv ,一个二维向量
  • 历史最优值 pbpb 和其坐标

同时还要维护全局最优解及其坐标

阅读更多

字符串 Hash

概述

你有一个字符串 ss 你有一个神奇的函数 ff 可以使

  • f(s)Rf(s)\in R
  • s1s2s_1 \neq s_2 时基本上不会出现 f(s1)=f(s2)f(s_1)=f(s_2)

一种常见的 ff 构造方法是

f(s)=i=1nsirationi f(s)=\sum_{i=1}^n s_i\cdot ratio^{n-i}
阅读更多

虚树

简介

对于这样一类题目:给定一棵 nn 点树,有 qq 个询问,每个询问关于树上 kik_i 个点, n,q,k105n,q,\sum k \leq 10^5

朴素做法 O(qn)O(qn)

由于一些题目的特殊,发现求解只需保留树的结构,故可以重构一棵虚树

虚树包含了所有的 kk 个关键点和它们两两之间的 LCA,这样可以保证这虚树的节点个数 <2k< 2k,保证了复杂度

example from Sengxian's Blog

阅读更多

快速傅里叶变换 Fast Fourier Transform

前置技能

虚数

i2=1i=1 i^2 = -1\\ i = \sqrt{-1}

复数

任意复数 zz 都可表示为 a+bi(a,bR)a+bi(a,b\in R)

aa 叫做实部,定义 Re(z)=a\mathrm{Re}(z) = a

bb​ 叫做虚部,定义 Im(z)=b\mathrm{Im}(z) = b​

阅读更多

更优雅的线段树

线段树原理非常简单,但如何优雅的实现却是一个问题

线段树简介

线段树就是把一个序列原有 nn 个点扩充为 nlognn\log n 个点,保持一定结构进行加速

每个点有

  • 自己管辖的区域
  • 两个拼起来是自己的子节点( self=lsonrsonself = lson \cup rson
  • 子节点不交( lsonrson=lson \cap rson = \varnothing
  • 可以快速的区间合并
阅读更多