学习笔记(10)数学各项
原根
实际上求法比较的暴力,一句话说就是判定其存在后 \(O(n^{0.25})\) 地枚举最小的原根然后粗暴地找出剩下的原根
\((Updating \dots)\)
同余方程
\(Cipolla\) 算法
解决类似求 \(a^{k} \equiv b \bmod p\) 中给定 \(a,k,p\) 求 \(b\) 的问题
不过此算法只能求解二次剩余,高次剩余需要另外考虑算法
\((Updating \dots)\)
\((ex)BSGS\) 算法
解决类似求 \(a^{k} \equiv b \bmod p\) 中给定 \(a,b,p\) 求最小的 \(k \in Z^{*}\) 的问题
在 \((a, p) = 1\) 时因为满足柿子: \(a^{k} \equiv a^{k \bmod \phi (p)} \bmod p\),可以设 \(y = \lceil \sqrt{p} \rceil, x = my - n\),则有 \(a^{my - n} \equiv b \bmod p\)。将 \(a^{n}\) 除到另一边,最终得到: \(ba^{n} \equiv a^{my} \bmod p\),发现左侧 \(n \in \left[0, y \right)\) 可以直接枚举预处理,用哈希记录下所有相应的 \(n\),右侧直接枚举 \(m \in \left[0, y \right)\) 并判断是否有对应的 \(n\) 与之匹配即可,code::
点击查看代码
unordered_map<int, int> id;
int BSGS(int a, int b, int p){
a %= p, b %= p;
if(b == 1 % p) return 0;
int k = sqrt(p) + 1;
id.clear();
for(int y = 0, r = b; y < k; y++){
id[r] = y;
r = (1ll * r * a) % p;
}
int ak = qpow(a, k, p);
for(int x = 1, l = ak; x <= k; x++){
if(id.count(l)) return k * x - id[l];
l = (1ll * l * ak) % p;
}
return -INF;
}
然而当 \((a,p) \neq 1\) 时,需要每次将同余方程两边同除以 \(d = gcd(a,p)\) 直到 \((a,p) = 1\) 时可以用 \(BSGS\) 直接求解,中途的模数转换问题可以用 \(exGCD\) 解决(顺便复习一下,在欧拉的柿子 \(ax + by = c\) 中,一定有 \(gcd(a,b) | c\),在辗转相除直到形成 \(a'x+b'y = 1\) 时可以构造这样的一组可行解 \(x,y\) 的过程中可以手动设置边界 \(x = 1, y = 0\) 使得 \(exGCD\) 求出的是一组特殊解—— \(x_{0}\) 最小。同时在没有乘上 \(\frac{b}{gcd(a,b)}\) 时得出的 \(x\) 是 \(a\) 的逆元),可以极大的简化代码实现,code::
点击查看代码
int exGCD(int a, int b, int &x, int &y){
if(!b){
x = 1, y = 0;
return a;
}
int res = exGCD(b, a % b, y, x);
y -= (a / b) * x;
return res;
}
int exBSGS(int a, int b, int p){
a %= p, b = (b % p + p) % p;
if(b == 1 % p) return 0;
int x, y;
int d = exGCD(a, p, x, y);
if(d ^ 1){
if(b % d) return -INF;
exGCD(a / d, p / d, x, y);
return exBSGS(a, (1ll * (b / d) * x) % (p / d), p / d) + 1;
}
return BSGS(a, b, p);
}
普通生成函数OGF
严格来说只是一种“映射”表达,将两个序列通过系数与指数建立关系。这样的映射关系表达满足一般的运算关系,很巧妙的是,介于相邻编号的序列在指数上也相邻,因此求导或积分时相当于对其生成函数左移/右移并相应地补 \(0\)。一般来说针对技术问题,习惯用指数刻画选取个数(毕竟生成函数是 \(a_{0}x^{0} + a_{1}x^{1}+ a_{2}x^{2}\dots\) 的形式),而系数则用以刻画相应情况下的方案总数,以P10780 食物为例,对于每一种物品的选取方案,我们用生成函数的指数刻画选择个数,系数用以刻画贡献(可取则 \(1\),否则为 \(0\))
为了便于利用各种运算规则(最重要的是卷积)快速求出答案序列的生成函数,我们引入生成函数的封闭形式——在无穷级数下收敛得到的生成函数的值,然而 \(x\) 仍然没有实际意义。这样的形式利于在统计方案数时乘法原理的应用,可以将答案序列直接进行卷积——也就是计算其生成函数封闭形式的乘积。注意封闭形式等价于原生成函数,只是封闭形式并非多项式的形式,需要转换成多项式形式后再得到第 \(k\) 项的系数
展开封闭形式会需要用到一些技巧,常用二项式定理
二项式反演
\(Tips\)
我们都知道二项式定理的基本形式:
其中满足 \(k \in Z ^{*}\),但要是 \(k\) 为负数怎么办?这里假设 \(k\) 仍然为正整数:
具体可以通过泰勒公式求证,另外还有更加广义的牛顿二项式定理(例题:P10780 食物):
正文
一般来说有以下式子:
在题目中一般认为 \(G(i)\) 为最终满足“恰好”的答案,而 \(F(i)\) 为至少/至多情况下所直接钦定的答案,然而会有重复统计,因此并不满足 \(F(i) - F(i - 1) = G(i)\) 的柿子,每一项的重复贡献应带组合系数。其中上面两式为满足“至多”的定义情况,下面则为满足“至少”的定义情况。由于 \(F(i)\) 一般可以快速计算,于是答案 \(G(i)\) 可以利用二项式反演的柿子快速计算
莫比乌斯反演
前置知识:数论分块
基于一个巧妙的观察——
对于一个正整数 \(n\),所有形如 \(\lfloor \frac{n}{x} \rfloor\) 的数只有 \(O(\sqrt{n})\) 个
于是可以直接枚举这 \(O(\sqrt{n})\) 个值,其中对于每个 \(x \in [l,r]\) 都有 \(\lfloor \frac{n}{x} \rfloor = \lfloor \frac{n}{l} \rfloor\),证明如下:
枚举代码:
int lim = min(x, y);
for(int l = 1, r; l <= lim; l = r + 1){
r = min(x / (x / l), y / (y / l));
\\...
}
于是中间的部分可以类似于前缀和的预处理
部分重要数论函数
· \(id_{k}(n) = n ^ {k}\),其中一般记 \(id_{1}\) 为 \(id\),\(id_{0}\) 为常函数 \(1(n)\) 或 \(I(n)\)
· \(\epsilon(n) = [n = 1]\),单位函数,或称“单位元”
· \(\sigma_{k}(n)\) 因数函数,\(k\) 为指数,\(k=0\) 时表示因数个数
· \(\phi(n)\),欧拉函数,表示 \(n\) 以内与 \(n\) 互质的数的个数
· \(\mu(n)\),莫比乌斯函数,若有平方因子则为 \(0\),否则为 \(-1\) 的质因子个数次幂
前置知识:荻利克雷卷积
传统的生成函数式卷积是 \(O(n^{2})\) 的,而数论函数有自己特殊的卷积,即荻利克雷卷积(又称数论卷积),其主要形式如下:
性质:满足交换律,结合律
利用狄利克雷卷积可以得到几个有趣的性质:
正文
莫比乌斯反演主要建立在数论函数卷积的基础上,多用于快速求解结构类似于 \([gcd(x, y) = 1]\) 的数论函数的前缀和(时间复杂度 \(\leq O(n)\))
其中上文中第二条性质可以详细写为:
将 \(n\) 代为更常用的 \(gcd\) 可得:
而上式即为莫比乌斯反演的核心
一般来说只需要构造出 \([gcd[i,j] = 1]\) 的形式,然后就能将原本难以计算的式子转换为相对容易计算并预处理的数论函数(大多可以结合筛法预处理,一般埃筛或欧拉筛即可,规模太大需要杜教筛或者 \(Min-25\) 筛),似乎应用莫反的题目会有类似如下的重要构造:
当然个别题目可以直接用欧拉函数化简柿子简化计算,不一定必须使用莫反,部分原因在于下式:
后附线性筛求 \(\mu\) 与求 \(\phi\) 的方法,线性筛保证每个数只会被其最小的质因子筛到,对于积性函数而言是个不错的选择:
(求 \(\mu\))\(\Downarrow\)
点击查看代码
void pre(int k){
mu[1] = 1;
for(int i = 2; i <= k; i++){
if(!comp[i]) mu[i] = -1, prime[++tot] = i;
for(int j = 1; j <= tot && i * prime[j] <= k; j++){
comp[i * prime[j]] = 1;
if(i % prime[j] == 0) break;
else mu[i * prime[j]] = -mu[i];
}
}
}
点击查看代码
void pre(int k){
phi[1] = 1;
for(int i = 2; i <= k; i++){
if(!comp[i]) phi[i] = i - 1, prime[++tot] = i;
for(int j = 1; j <= tot && i * prime[j] <= k; j++){
comp[i * prime[j]] = 1;
if(i % prime[j] == 0){
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
else phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
杜教筛
前置知识
· 数论分块
· 狄利克雷卷积
· 莫比乌斯反演
正文
这类筛法主要用于在低于线性的复杂度内求解积性函数的前缀和,被称为“亚线性算法”
设 \(\sum_{i = 1}^{n}f(i) = S(n)\)
我们需要再寻找一个积性函数 \(g\),并且要求 \(g\) 与 \(f*g\) 的前缀和都易于求解
考虑他们的荻利克雷卷积的前缀和:
于是我们可以发现,\(g(1)S(n)\) 就等价于:
即杜教筛的核心柿子:
根据上式选取合适的积性函数 \(g\) 递归计算即可,加上记忆化之后的时间复杂度貌似是 \(O(n^{3/4})\) 的
代码如下(以求 \(\phi\) 的前缀和为例):
点击查看代码
ll gets_phi(ll x){
if(x <= 1e7) return sump[x];//小于一定范围的直接欧拉筛预处理
if(S_phi.count(x)) return S_phi[x];//map记忆化
ll res = 1ll * x * (x + 1) / 2ll;//phi * 1 = id
for(ll l = 2, r; l <= x; l = r + 1){//记得看一眼l是否会爆int
r = x / (x / l);
res -= 1ll * (r - l + 1) * gets_phi(x / l);
}
S_phi[x] = res;
return res;
}
第二类斯特林数
一般记 \(n\brace m\) 表示把 \(n\) 个不同的元素划分为 \(m\) 个无序非空集合的方案数,即第二类斯特林数
递推式
组合意义可证,考虑最后一个元素单独新开一个集合或者和前面的元素一起构成集合,那么可以得到:
通项公式
如果考虑二项式反演容斥一下空集个数,那么就有:
稍微改写一下上式得:
注意到后面的形式形如卷积,所以可以分别构造两个生成函数,然后直接利用 \(NTT\) 等加速卷积的过程 \(O(n\log n)\) 求斯特林数的一行(求一列的话需要用到指数生成函数 \(EGF\),不会)
下降幂公式
组合意义可证:
考虑把 \(n\) 个不同的元素放进 \(x\) 个不同的可空集合中,方案数为 \(x^{n}\)
那么先从 \(x\) 个集合中选出 \(i\) 个,然后再把 \(n\) 个元素有序地划分为 \(i\) 个部分,每个部分依次放进每个集合,组合意义与 \(x^n\) 相同
另外也可以用该公式结合二项式反演推导第二类斯特林数的通项公式:
回代后移项即可
\(\Large min/max\) 容斥
简单来说就是能通过子集的最大/小值统计出第 \(k\) 小/大的值,柿子如下:
\(min/max\) 反过来也是成立的,其中 \(S\) 表示原集合,\(T\) 是枚举的子集
\((Updating \dots)\)
拉格朗日插值
原理:\(n + 1\) 个函数图像上的点对可以唯一的确定一个 \(n\) 次函数
本来数据规模允许的情况下是可以直接高斯消元 \(O(n^{3})\) 做的,然而这样往往因写法导致各种精度问题。于是可以利用拉格朗日插值的基本公式直接 \(O(n^{2})\) 的求解某一点的值(之所以叫插值就是因为只能 \(O(n^{2})\) 地求一个点)
求函数 \(f(x)\) 在 \(k\) 处取值的基本公式如下(已知 \(n\) 个点的坐标 \((x, y)\)):
若这 \(n\) 个点在横坐标上连续的话则可以将拉插优化到 \(O(n)\),首先将上式中的 \(x_{i}\) 换为 \(i\),得到:
接着考虑用前后缀积优化掉 \(\Pi _{j \neq i} \frac{k - j}{i - j}\),记:
于是得到:
(这个计算技巧还是挺不错的)