MIT-18.06
Lec 4 A = LU
\[ (AB)^{-1} = B^{-1}A^{-1} \\ (A^{T})^{-1} = (A^{-1})^{T}\\ \]
\[ (AB)^{-1} = B^{-1}A^{-1} \\ (A^{T})^{-1} = (A^{-1})^{T}\\ \]
\[ \sin^2x + \cos ^ 2 x = 1\\ \]
\[ \sin'x = \cos x\\ \cos'x = - \sin x\\ \tan'x = \frac{1}{\cos^2x} = \sec^2x\\ \arctan' x = \frac{1}{1+x^2}\\ \]
\[ \frac{\text d}{\text d x}\ln x = \frac{1}{x}\\ \frac{\text d}{\text d x}e^x = e^x\\ \frac{\text d}{\text dx}a^x = \ln a \cdot a^x\\ \frac{\text d}{\text d x} \ln u = \frac{1}{u}\frac{\text d u}{\text d x} \]
如图, \(P\)点的导数便是过\(P\)点的切线(tangent line)的斜率, 记作\(f'(P)\).
什么样子的线是切线? 我们先看由点\(PQ\)构成的割线(secant line). 当\(PQ\)两点的距离趋于\(0\)时, 割线与切线便无限接近.
如图. 可得 \[ f'(x) = \lim_{\Delta x \to 0} \frac{f(x_0 + \Delta x) - f(x_0)}{\Delta x} \]
导数(牛顿记法): \(f'\)
导数(莱布尼兹记法): \(\frac{\text df}{\text dx},\frac{\text dy}{\text d x},\frac{\text d}{\text dx}f,\frac{\text d}{\text dx}y\)
一个有用的公式(推导见手写笔记): \[ \frac{\text d}{\text d x}x^n = n \cdot x^{n-1} \]
我们用\(\frac{\Delta y}{\Delta x}\)来计量某个函数在一段时间内的平均变化率. 当\(\Delta x \to 0\)时, 这便变成了瞬时变化率.
简单的极限可以直接带入运算:
但是对于 \[ \lim_{\Delta x \to 0}\frac{\Delta f}{\Delta x} \] 它不是一个简单极限. 因为我们不能将\(\Delta x = 0\)代入运算, 这样永远会得到\(\frac{0}{0}\). 注意: \[ \lim_{x \to x_0} \] 暗示了\(x \neq x_0\).
我们用以下两个记号来表示左极限和右极限: \[ \lim_{x\to x_0^-} (左极限, 此时x < x_0)\\ \lim_{x\to x_0^+} (右极限, 此时x > x_0)\\ \]
我们称\(f(x)\)在\(x_0\)连续, 当且仅当 \[ \lim_{x \to x_0}f(x) = f(x_0) \] 这意味着:
如图, \(\lim_{x \to 0^+}f(x) = 1\), 但是\(f(0) = 0\). 因此该函数在点\(0\)不连续.
以下是不连续的几种情况:
该点的左右极限存在且相等, 但与该点的函数值不等(或该点的函数值未定义)
左右极限均存在, 但不相等.
\(x = 0\)时, 左极限为负无穷, 右极限为正无穷.
注意导数的图像和原函数的图像没有什么相关性. 而且对一个奇函数求导, 它的导数一定是一个偶函数.
\[ 如果f在x_0处可导, 那么f必定在x_0处连续. \]
\[ \lim_{\theta \to 0}\frac{\sin\theta}{\theta} = 1\\ \lim_{\theta\to0}\frac{1 - cos\theta}{\theta} = 0 \]
证明:
\[ \frac{\text d}{\text d x}\sin x = \cos x\\ \frac{\text d}{\text d x}\cos x = -\sin x\\ \]
推导过程:
加法法则 \[ (u+v)' = u' + v' \]
乘积法则
\[ (uv)' = u'v + uv' \]
除法法则(quotient rule) \[ (\frac{u}{v})' = \frac{u'v - uv'}{v^2}(v \neq 0) \]
证明: 加法法则
证明: 乘法法则
几何上的理解: \((uv)' \cdot \Delta x = (u + \Delta u)(v + \Delta v) - uv\), 即图中粉色, 白色, 黄色部分之和. 又因为\(\Delta u \cdot \Delta v \approx 0\), 所以有 \[ (uv)' \approx \frac{u \Delta v + v \Delta u}{\Delta x} \\ = uv’ +u'v \]
\[ \frac{\text{d}y}{\text{d}t} = \frac{\text{d} y}{\text{d}x} \cdot \frac{\text{d}x}{\text{d}t} \]
有些函数(例如\(x^2 + y^2 = 1\))不具有明显的\(y = f(x)\)形式, 但是能通过变形得到(本例可得\(y = \sqrt{1 - x^2}\)). 这样的函数称为隐函数. 对隐函数求导, 可在方程两边同时对\(x\)求导, 得到一个包含\(\frac{\text{d}y}{\text{d}x}\)的式子, 再求解\(\frac{\text{d}y}{\text{d}x}\)即可.
若\(y = f(x),g(x) = y\), 则\(g\)是\(f\)的反函数, 记作\(f^{-1}\). 函数和它的反函数关于\(y = x\)对称.
见课程pdf和手写笔记
### 对数微分(Logarithmic Differentiation)
(普林斯顿微积分读本修订版,人民邮电出版社,ISBN: 978-7-115-43559-0, P101)
\[ f(x) \approx f(x_0) + f'(x_0)(x - x_0) \]
注意上表近似符号左边和右边的函数. 如果\(f(x_0) + f'(x)(x - x_0)\)比\(f(x)\)更容易计算, 便可以用线性近似来加速计算.
一阶(线性)近似可能精度不够, 这时便需要二阶或更高阶的近似. \[ f(x) \approx f(x_0) + f'(x_0)(x - x_0) + \frac{f''(x_0)}{2}(x-x_0)^2 \]
关于为什么二阶导的系数是\(\frac{1}{2}\):
可以看出, 二阶近似的效果更好:
基础的结论:
Lecture 10: Curve Sketching
利用\(f'\)和\(f''\)的符号来画出\(f\)的近似图像
画图的一般步骤:
当\(f'(x_0) = 0\)时, 点\(x_0\)称为驻点(临界点, critical points).
当\(f''(x_0) = 0\)时, 点\(x_0\)称为拐点(inflection point).
[TOC]
答案不取模,亲人两行泪
212370440130137957
(↑这是一个很大的质数)
19260817
(↑这是一颗很大的子弹)
判断同余时用\((a - b) \mod m== 0\)
cout不使用科学计数法输出,设置精度: 1
2cout.setf(ios::fixed,ios::floatfield);
cout.precision(2);
# 杂项 | ||
## 神秘结论 | ||
\[ \sum_{i = 1}^n (d(i))^2 \ll n \log^3 n (d为因子个数) \] | ||
## 大整数 | ||
(我也不知道从哪copy)的 | ||
|
||
使用例: | ||
|
||
## 随机数发生器 | ||
注意不要用Windows下的\(rand()\) | ||
|
||
### 均匀分布 | ||
|
||
## 计时 | ||
|
||
# 数论 | ||
## 同余式 | ||
同余属于等价关系, 满足自反、对称、传递三大关系 \[ \begin{align} a \equiv a \pmod m\\ a \equiv b \pmod m \iff b \equiv a \pmod m\\ a \equiv b \pmod m \and b \equiv c \pmod m \Rightarrow a \equiv c \pmod m \end{align} \] 加、减、乘与一般运算一致. 若\(a \equiv b \pmod m\)且\(c \equiv d \pmod m\), 则有 \[ a+c \equiv b+d \pmod m\\ a-c \equiv b-d \pmod m\\ a*c \equiv b*d \pmod m \] 除法比较特殊. 若\(ac \equiv bc \pmod m\), 则 \[ a \equiv b \pmod{\frac{m}{\gcd(c,m)}} \] | ||
其它性质: | ||
\[ \begin{align} ax \equiv 0 \pmod p \iff x \equiv 0 \pmod{\frac{p}{\gcd(p,a)}} \end{align}\\ x \% b == 0 \and \frac{x}{b} \equiv 0 \pmod p \iff x \equiv 0 \pmod{bm}\\ a \equiv b \pmod m \iff a^n \equiv b^n \pmod m\\ a \equiv b \pmod m \and n|m \Rightarrow a \equiv b \pmod n \] | ||
## 快速幂 | ||
计算\(x^d \mod p\) | ||
注意\(a^b % p = ((a % p)^b) % p\) | ||
|
||
## 一次不定方程 | ||
\(\sum_{i = 1}^n a_i \cdot x_i = c\)有整数解当且仅当\(\gcd(a_1,a_2,...,a_n) | c\) | ||
## GCD & exGCD | ||
x,y,d为全局变量.求x和y使得 \(ax + by = d\) 且 \(|x| + |y|\)最小. | ||
其中 \(d = gcd(a,b)\) | ||
|
||
## 逆元 | ||
返回模p下a的逆.不存在则返回-1. | ||
|
||
返回模p下1...n的逆.保存在invs里 | ||
|
||
## a,b不互质时 | ||
\[ \frac{a}{b} \mod m = \frac{a \mod (mb)}{b} \] | ||
证: \[ \begin{align} &\frac{a}{b} = km + x (x < m)\\ \Rightarrow \space &a =kbm + bx\\ \Rightarrow \space &a \mod (bm) = bx\\ \Rightarrow \space &\frac{a \mod (bm)}{b} = x\\ \Rightarrow \space &\frac{a \mod (bm)}{b} = \frac{a}{b} \mod m \end{align} \] | ||
## 中国剩余定理(CRT) | ||
求同余方程组 \[ \begin{cases} x &\equiv a_1 \pmod {m_1} \\ x &\equiv a_2 \pmod {m_2} \\ &\vdots \\ x &\equiv a_k \pmod {m_k} \\ \end{cases} \] 在\([0,\prod_{i = 1}^nm_i])\)中的解. 要求任意\(m_i\)两两互质 | ||
输入: int n 方程组个数, ll a[] ll m[] ,方程组的参数和模数 |
||
复杂度:\(O(nlogM)\), 其中\(M\)与\(m_i\)同阶 | ||
输出: 方程组在\([0,\prod_{i = 1}^nm_i])\)中的解. | ||
前置: 逆元 | ||
调用 CRT(n,a,m) |
||
|
||
## Eratosthenes筛 | ||
|
||
## 线性筛 | ||
筛素因子时记得直接去掉最后一个素因子, 否则复杂度不对 | ||
### 线性筛素数 | ||
|
||
### 线性筛\(\varphi\) | ||
\(\varphi(n)\): 小于等于\(n\)的正整数中与\(n\)互质的数个数 | ||
\(\varphi\)的一些性质(以下\(p\)代表素数) \[ \varphi(p) = p-1\\ 如果i \% p == 0, \varphi(i*p) = \varphi(i)*p\\ 否则\varphi(i*p) = \varphi(i)*(p-1) \] | ||
phi[n]为\(\varphi(n)\),prime里存的素数 | ||
|
||
### 线性筛\(\mu\) | ||
\[ \mu(n)= \begin{cases} 1&n=1\\ 0&n\text{ 含有平方因子}\\ (-1)^k&k\text{ 为 }n\text{ 的本质不同质因子个数}\\ \end{cases} \] | ||
|
||
## 杜教筛 | ||
详细请见杜教筛笔记. | ||
杜教筛快速筛\(\varphi\)和\(\mu\)的前缀和.\(maxn = n^{\frac{2}{3}}\)时复杂度为\(O(n^{\frac{2}{3}})\) | ||
|
||
## 欧拉函数\(φ(x)\) | ||
|
||
### 线性筛\(φ(x)\) | ||
|
||
## 有理数取模 | ||
求\(a/b\) % \(p\) (p为质数) | ||
由费马小定理\(b^{p-1} ≡ 1 \pmod p\) 可得 \(b^{p-2} ≡ b^{-1} \pmod p\) | ||
故只需求\(a*b^{p-2} \bmod p\) | ||
a,b过大则先膜一下 | ||
## 欧拉定理 | ||
若\(a,p\)互质,则有 | ||
\(a^{φ(p)} ≡ 1 \pmod p\) | ||
### 扩展欧拉定理(无需a,p互质) | ||
\(b ≥ φ(p)\)时: | ||
\(a^b ≡ a^{b \space \bmod \space φ(p) +φ(p)} \pmod p\) | ||
例题: 给出\(w_1,w_2,..w_n\)和\(q\)组询问\(l \space r\). 求\(w_l^{w_{l+1}^{...^{w_r}}} \pmod p\)的值 | ||
|
||
## 原根 | ||
proot(p) 返回素数p的原根 |
||
|
||
## 离散对数(BSGS) | ||
### 原版 | ||
只支持模数为\(p\)的情况. | ||
bsgs(ll a,ll b,ll p) 返回最小的\(x\)使得\(a^x \equiv b\pmod{p}\), 不存在则返回\(-1\). 要求\(p\)为素数. 时间复杂度\(O(\sqrt p \cdot \log(\sqrt{p}))\) |
||
|
||
求最小的正整数\(n\)使得\(a^n \equiv 1 \pmod m\) | ||
|
||
### 扩展版 | ||
\(p\)可以是任意模数. 前置:bsgs exgcd inv |
||
|
||
## 拉格朗日插值 | ||
px,py为点坐标,p为模数,k是L(k)那个k inv为逆元 | ||
返回L(k) | ||
|
||
### 线性求\(\sum_{i = 0}^{n}i^k\) | ||
(返回L(k)的值,多项式次数为n.按需更改yi的值,复杂度\(O(k)\)) | ||
(注意求\(\sum_{i = 0}^{n}i^k\)需要调用line_lagrange(k+2,n) ) | ||
|
||
## 卢卡斯定理 | ||
Lucas(n,m) = C(n%p,m%p)*Lucas(n/p,m/p)%p | ||
p需为素数,fac[]为预处理的阶乘,时间复杂度大约是\(O(p + mlogm)\)(预处理需要\(O(p)\)) | ||
|
maxbit取值域位数
insert返回0则说明现有基可以表示x
qmax返回异或最大值
1 | struct LinerBasis{ |
设\(n\)为输入项数, 复杂度\(O(n^2\log n)\). 对于\(k\)阶递推式(例如斐波那契数列为2阶递推式)至少需要前\(2k\)项
1 | #include<bits/stdc++.h> |
第二类Stirling数\(S(n,m)\)计数的是把\(n\)个元素的集合划分到\(m\)个不可区分的盒子中且没有空盒子的划分个数.
\(S(n,n) = 1\), \(S(n,0) = 0\space(n > 0)\)
递推式: \(S(n,m) = m \cdot S(n-1,m) + S(n-1,m-1)\)
\[
S(n,m) = \sum_{i = 0}^m \frac{i^n}{i!}\cdot\frac{(-1)^{m-i}}{(m-i)!}
\] 用NTT求解即可. finv[i]
为\(i!\)的逆元, 需要线性预处理出来. mul()
见NTT板子. 时间复杂度\(O(n \log n)\)
1 | Poly Stirling_row(int n){ |
\[ \sum_{n = k}^{\infin}S(n,k) \cdot \frac{x^n}{n!} = \frac{(e^x - 1)^k}{k!} \]
因而有 \[ S(n,k) = \frac{n!}{k!}\cdot[n](e^x - 1)^k \] 多项式快速幂即可. 时间复杂度\(O(n \log n)\)
1 | Poly Stirling_Col(int n,int k){ |
Bell数(\(B(n)\))计数的是将一个有\(n\)个元素的集合划分成任意个非空子集的方案数. 显然有\(B(n) = \sum_{i = 1}^n S(n,i)\)
前10个Bell数为1 2 5 15 52 203 877 4140 21147 115975
\[
B(n) = [n]e^{e^x - 1}
\] 多项式指数函数即可. 时间复杂度\(O(n\log n)\)
1 | Poly Bell(int n){ |
两人轮流取两堆筹码,其中取法有两种:取走一堆中任意个筹码,或从两堆中取走相同数目的筹码。取完所有筹码的一方获胜。
(1为先手胜)
1 | if(n1>n2) swap(n1,n2); |
注意:vector版的矩阵会比数组慢起码2倍,时间吃紧的时候慎用
数组版:
1 | #define FOR(i,a,b) for(int i = a;i <= b;i++) |
vector版:
1 | typedef vector<vector<int> > Matrix; |
每一行每一列有且仅有一个\(1\), 其余元素为\(0\)的矩阵为置换矩阵.
置换矩阵的逆是它的转置.
除第一行外, 其余每行都是上一行循环右移一格的矩阵是循环矩阵. 循环矩阵的乘积仍是循环矩阵.
因此只需保存和计算循环矩阵的第一行, 两个循环矩阵相乘复杂度为\(O(n^2)\)
1 | typedef vector<int> cirMatrix;//循环矩阵只需保存第一行 |
输入一个\(n\)行\(n+1\)列的矩阵,其中\(A[i][n]\)代表等式右边第\(i\)个值. 复杂度\(O(n^3)\)
gauss(矩阵,n)
返回1有解, 返回0无解
1 | bool gauss(vector<vector<ld> > & A,int n){ |
时间复杂度\(O(\frac{n^3}{w})\), \(w = 32或64\), 取决于评测机位数
1 | const int maxb = 1005; |
1 | const ld eps = 1e-9; |
输入一个vector矩阵, 输出行列式模\(p\)的值. 均摊复杂度\(O(n^3 + n^2\log n)\)
1 | int caldet(vector<vector<int> > & a,int n,int p){ |
矩阵树定理用于求解图的生成树个数. 来源: https://oi-wiki.org/graph/matrix-tree/
设\(t(G)\)为图\(G\)的全部生成树边权积的和(当边权为1时即为全部生成树的个数)
即 \[ t(G) = \sum_{T \in G} \prod_{e \in T}w_e \] 其中\(T\)代表图\(G\)的生成树, \(e\)为生成树上的边,\(w_e\)为边的权值.
以下所讨论的图允许重边但不允许自环(去掉自环即可).
设\(D\)为无向图\(G\)的度数矩阵, \(A\)为无向图的邻接矩阵. \(deg(x)\)为点\(x\)的度数.
则\(D[i][i] = deg(i) \cdot[i == j]\), \(A[i][j] = |i与j间的连边权值之和|\)
设\(Laplace\)矩阵\(L = D - A\). \(L_r\)为\(L\)的\(n-1\)阶主子式(即将矩阵\(L\)去掉第\(r\)行第\(r\))列.
则对任意的\(r\), \(L_r\)的行列式为该无向图的生成树个数. 即\(t(G) = det(L_r)\)
若\(\lambda_1,\lambda_2,...,\lambda_{n-1}\)为\(L\)的\(n-1\)个非负特征值, 则有 \[ t(G) = \frac{1}{n}\prod_{i = 1}^{n-1}\lambda_i \]
有向图的生成树分两种: 根向, 所有边均由儿子指向父亲; 叶向, 所有边均由父亲指向儿子.
设\(D^{out}\)为有向图\(G\)的出度矩阵, \(D^{in}\)为有向图\(G\)的入度矩阵, 两者与无向图形式相同.
\(A\)为有向图邻接矩阵, \(A[i][j] = |i指向j的边数|\)
设入度Laplace矩阵\(L^{in} = D^{in} - A\), 出度Laplace矩阵为\(L^{out} = D^{out} - A\)
则以\(k\)为根的叶向生成树个数为\(t^{leaf}(G,k) = det(L_k^{in})\), 根向生成树个数为\(t^{root}(G,k) =det(L_k^{out})\).
若要求所有生成树, 枚举\(k\)并求和即可.
对有向图无向图均适用. 将带权边转化为无权的重边, 重边次数为边权即可.
通过图中所有边恰好一次且行遍所有顶点的回路称为欧拉回路.
具有欧拉回路的无向图或有向图称为欧拉图.
设 \(G\) 是有向欧拉图,那么 \(G\) 的不同欧拉回路总数 \(ec(G)\) 是 \[ ec(G) = t^{root}(G,k)\prod_{v\in V}(\deg (v) - 1)! \]
注意,对欧拉图 \(G\) 的任意两个节点 \(k, k'\),都有 \(t^{root}(G,k)=t^{root}(G,k')\),且欧拉图 \(G\) 的所有节点的入度和出度相等。
以\(Fibonacci\)为例:
\(f(1) = f(2) = 1,f(n) = f(n-1) + f(n-2),(n \geq 10^9)\),求\(f(n)\)
设 \[ F_i = \begin{bmatrix} f_{i+1}\\ f_i \end{bmatrix}, F_0 = \begin{bmatrix} f_1\\ f_0 \end{bmatrix} \] 且\(F_i = A^i \cdot F_0\)
则有 \[ A = \begin{bmatrix} 1 & 1\\ 1 & 0 \end{bmatrix} \] 一般地, 对于递推式\(f(i) = \sum_{i = 1}^{k}a_i \cdot f(i-k)\)
有: \[ F_i = \begin{bmatrix} f(i+k-1)\\ f(i+k-2)\\ ...\\ f(i) \end{bmatrix}, A = \begin{bmatrix} a_1 & a_2 & a_3 & ... & a_k\\ 1 & 0 & 0& \cdots & 0\\ 0 & 1 & 0 & \cdots & 0\\ 0 & 0 & 1 & \cdots & 0\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ 0 & 0 & \cdots & 1 & 0 \end{bmatrix} \] 且\(F_n = A^n \cdot F_0\)
1.无权图求从\(i\)到\(j\)经过\(k\)条边的方案数:求邻接矩阵的\(k\)次幂即可
2.有向图求包含\(k\)条边的最短路:求邻接矩阵的\(k\)次幂,把普通矩阵乘法改为Floyd
给出数列\(\{a_i\},\{b_i\}\),求一组\(w_i \in \{0,1\}\), 使得 \[ \frac{\sum_{i = 1}^na_i \cdot w_i}{\sum_{i = 1}^nb_i \cdot w_i} \] 最大或最小
或给出\(n\)个物品,每个物品有两个权值\(a_i\)和\(b_i\), 选择任意个物品使得\(\frac{\sum a_i}{\sum b_i}\)最大
\[ \frac{\sum_{i = 1}^na_i \cdot w_i}{\sum_{i = 1}^nb_i \cdot w_i} \ge M \Leftrightarrow \sum_{i = 1}^n w_i \cdot (a_i - M\cdot b_i) \ge 0 \]
因此只需判断$_{i = 1}^n w_i (a_i - Mb_i) \(的最大值是否非负即可对\)M$二分.
将上一轮的答案作为输入进行迭代.注意此时答案应为\(\frac{\sum_{i = 1}^na_i}{\sum_{i = 1}^nb_i}\), 而不是变形后的式子.
给出\(n \leq 10^3\)个点的横纵坐标和高度, 两点间距离为欧几里得距离, 价值为高度之差绝对值. 所有点构成一个完全图. 求一棵生成树使得
距离/价值比最小.
此题是有一定限制的0-1分数规划(所选的"物品"构成一棵生成树). 直接求边权为\(w_i \cdot (a_i - M\cdot b_i)\)的最小生成树, 判断是否有解即可二分.
由于是完全图, 采用不加优化的Prim求解.
复杂度大概是\(O(n^2 \log((R - L)*2^{-eps}))\)
由于二分法较慢, 本题需要猜一个答案上界(100)才能通过=-=
用时2610ms
1 | //POJ2728 |
求生成树时仍按边权\(w_i \cdot (a_i - M\cdot b_i)\)来计算. 但统计答案则计算\(\frac{\sum_{i = 1}^na_i}{\sum_{i = 1}^nb_i}\)
跑的飞快(250ms
)
1 | //POJ2728 |
复数类操作:
1 | complex<double>m; |
包括手写复数类, DFT与IDFT, 多项式乘法.
复数类: \(x\)为实部, \(y\)为虚部
FFT(Comp * A,int siz,int type)
A: 复数数组, 全局变量, 为待求解的多项式
siz: 多项式长度. 需保证为2的次方
type: 1为DFT, -1为IDFT
Poly mul(Poly A,Poly B)
多项式乘法. 输入两个多项式\(A,B\), 长度分别为\(n,m\)(也就是\(n-1,m-1\)阶). 返回一个长度\(n - m + 1\)的多项式, 为\(A,B\)的乘积.
Poly
为vector<int>
.
!!!切记在点值表示法下相乘时, 两个IDFT的长度必须相同!!!
1 | typedef double ld; |
\[ C[i] = \sum_{j = 0}^iA[j]B[i-j] \]
卷积等同于多项式乘积
从OI-wiki抄来的, 用法和上面的FFT一致. 记得自己手写一遍
1 | const int maxn = 6e6+10; |
与FFT的区别在点值表示法相乘时, NTT需要取模. 另外注意原根需与模数对应
1 | for(int i = 0;i < (int)A.size();i++)A[i] = 1ll*A[i]*B[i]%p; |
设有\(n\)个多项式, 第\(i\)个为\(Func[i]\)
就嗯分治完事了. 时间复杂度\(O(n\log^2 n)\).
1 | vector<Poly>Func; |
板子来源: Siyuan -「算法笔记」多项式求逆
调用invP(Poly A)
,返回模\(x^n\)意义下\(A\)的逆. 其中\(A\)是一个\(n-1\)次多项式(实际上是一个长度为\(n\)的vector<int>
)
时间复杂度: \(O(nlogn)\)
1 | int P[maxn],Q[maxn]; |
derP求导, intP积分, 复杂度均为\(O(n)\). 其中积分需要先预处理逆元inv[]
1 | Poly derP(Poly A,bool samelen = 1){ |
输入\(A(x)\), 求\(B(x) \equiv ln(A(x)) \pmod {x^n}\). 两边同时求导得: \[ B'(x) = \frac{A'(x)}{A(x)}\text dx \] 再积分: \[ B(x) = ln(A(x)) = \int B'(x)\text dx = \int\frac{A'(x)}{A(x)}\text dx \] 需要求逆,求导和积分, 需保证\(A[0] = 1\). 复杂度\(O(n\log n)\)
1 | Poly lnP(Poly A){ |
前置: 求\(\ln\)
和求逆基本相同, 返回\(e^{A(x)}\). 复杂度\(O(nlogn)\)
1 | Poly expP(Poly A){ |
求\(A(x)^k\), 显然有\(A(x)^k = e^{k\cdot ln(A(x))}\)
保证\(A[0] = 1\)时:
1 | Poly powP(Poly A,int k){ |
\(A[0] \neq 1\)时:
首先判断是否有\(k \ge n\)且\(A[0] == 0\). 若是, 则返回一个全为\(0\)的多项式. 注意, 必须在取模前判断大小, 且下面的模板没有包含这部分内容
然后对指数取模, 若输入的指数\(k > p\), 则令k1 = k % p
, k2 = k % (p - 1)
(即\(k2\)对\(\varphi(p)\)取模)
1 | Poly powP(Poly A,int k1,int k2){ |
求\(A[]\)的\(k\)阶差分/前缀和. 设\(A\)的OGF: \(F(x) = \sum_{i = 0}^{\infin}A_ix^i\)
显然前缀和是\(F(x)\cdot\sum_{i=0}^{\infin}x^i = F(x)\cdot \frac{1}{1-x}\), 差分是\(F(x)\cdot(1-x)\)
那么\(k\)阶前缀和是\(F(x)\cdot (\frac{1}{1-x})^k\), 差分是\(F(x)\cdot(1-x)^k\). 复杂度为\(O(nlogk)\), 但常数巨大且很不好写.
另有结论:
前缀和\(F(x)\cdot\sum_{i = 0}^nC_{k+i-1}^ix^i\)
差分\(F(x)\cdot\sum_{i=0}^n(-1)^iC_n^ix^i\)
复杂度\(O(n\log n)\). 使用前先预处理逆元.
1 | Poly presumP(Poly A,ll k){ |
给出\(f(x)\)和\([L,R]\), 对\(a[L...R]\)分别加上\(f(1),f(2),...,f(R-L+1)\).
结论: \(k\)阶多项式的\(k+1\)阶差分余项为常数. 对\(k+1\)阶差分后的余项做\(k+1\)次前缀和即可还原原多项式.
为了维护该操作, 我们先对原数组做\(k+1\)阶差分, 然后算出\(f(1),...,f(k+1)\)的\(k+1\)阶差分. 此后可以按一般的差分来维护.
细节见代码
1 | int d[maxn]; |
(本质不同即将序列排序后两个序列不相同) \[ C_{n+m-1}^{m-1} \]
\[ C_n^0 = 1\\ C_n^i = C_n^{i-1}\cdot \frac{n-i+1}{i}\\ C_{n+i-1}^i = C_{n+i-2}^{i-1}\cdot \frac{n+i-1}{i} \]
$$ \[\begin{align} \sum_{i = 0}^n C_n^i &= 2^n\\ \sum_{i = 0}^n C_n^i \cdot x^i &= (x+1)^n(二项式定理)\\ \sum_{i = 0,i += 2}^n C_n^i &= 2^{n-1}\\ \sum_{i=1}^nC_n^i \cdot i &= n \cdot 2^{n-1}\\ \sum_{i = 0}^r C_m^i \cdot C_n^{r - i} &= C_{n+m}^r\\ \sum_{i = 0}^n (C_n^i)^2 = C_{2n}^n\\ \end{align}\] $$
\[ \sum_{i = 1}^ni*m^{n-i} *C(n,i) = n(m+1)^{n-1} \]
\[ f(x) = \sum_{i = 0}^{\infin}h_ix^i \]
\[ \begin{align} \sum_{i=0}^nx^i &= \frac{1-x^{n+1}}{1-x} (等比数列求和)\\ \sum_{i=0}^\infin x^i &= \frac{1}{1-x}\\ \sum_{i = 0}^\infin x^{c\cdot i} &= \frac{1}{1 - x^c}\\ \sum_{i = 0}^{\infin}(i+1)\cdot x^i &= (\frac{1}{1-x})^2 \end{align} \]
\[ g(x) = \sum_{i=0}^{\infin}h_x\cdot\frac{x^i}{i!} \]
\[ \begin{align} \sum_{i = 0}^{\infin}\frac{x^i}{i!} & = e^x\\ \sum_{i = 0}^{\infin} (-1)^i \frac{x^i}{i!} & = e^{-x} \\ \sum_{i = 0,i +=2}^{\infin}\frac{x^i}{i!} &= \frac{e^x + e^{-x}}{2}\\ \sum_{i = 1,i += 2}^{\infin}\frac{x^i}{i!} & = \frac{e^x - e^{-z}}{2}\\ \sum_{i = 1}^{\infin}(-1)^{i+1} \frac{x^i}{i} & = \ln{(1+x)} \end{align} \]
设\(f(x) = \lfloor \frac{k}{x} \rfloor\)
\(f(x)\)分布呈块状,对于任意一个\(i\),有最大的\(j = \lfloor \frac{k}{\lfloor \frac{k}{i} \rfloor} \rfloor\),使得\(f(i) == f(i+1) == ... = f(j)\)
对于类似\(\sum_{i = 1}^{n}\lfloor \frac{k}{i} \rfloor\)的式子,可分块在\(O(\sqrt{n})\)时间内计算完毕
代码:
1 | for(int l = 1;l <= n;l = r+1){ |
对于向上取整, 有 \[ \lceil \frac{k}{x} \rceil = \lfloor \frac{k - 1}{x}\rfloor + 1 \]
设\(f,g\)为两个数论函数,其狄利克雷卷积\(f*g\)为: \[ f \ast g(n) = \sum_{d|n}f(d)g(\frac{n}{d}) \]
$$
\[ \sum_{d|n}^n\varphi(d) = n \]
\[ [\gcd(i,j) == 1] = \epsilon(\gcd(i,j) = \sum_{d|\gcd(i,j)}\mu(d) \]
\[ \begin{align} &\sum_{i=1}^n\gcd(i,n)\\ &=\sum_{d|n}d\sum_{i=1}^n[\gcd(i,n)==d]\\ &=\sum_{d|n}d\sum_{i=1}^{\frac{n}{d}}[\gcd(i,n)==1]\\ &=\sum_{d|n}d\cdotφ(\frac{n}{d}) \end{align} \]
\[ \begin{align} &\sum_{i = 1}^n\sum_{j=1}^n\gcd(i,j)\\ &= \sum_{d=1}^nd\sum_{i=1}^n\sum_{j=1}^n[gcd(i,j) == d]\\ &=\sum_{d=1}^nd\sum_{i=1}^{\frac{n}{d}}\sum_{j=1}^{\frac{n}{d}}[gcd(i,j)==1]\\ &=\sum_{d=1}^nd(2\cdot\sum_{i=1}^{\frac{n}{d}}\varphi(i) - 1) \end{align} \]
关于最后一步,我们设\(f(x) = \sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(i,j)==1]\)
手画一下,容易看出\(f(x) = 2\cdot\sum_{i=1}^n\varphi(i) - 1\)
另一种做法:
我们知道\(\varphi\)有这么一个性质,\(\sum_{d|n}^{n}\varphi(d) = n\)
也就是\(\sum_{d|\gcd(i,j)}^{n}\varphi(d) = \gcd(i,j)\)
那么: \[ \begin{align} &\sum_{i = 1}^n\sum_{j = 1}^n\gcd(i,j)\\ &=\sum_{i=1}^n\sum_{j=1}^n\sum_{d|\gcd(i,j)}\varphi(d)\\ &=\sum_{d = 1}^n\varphi(d)\sum_{i=1}^n[d|i]\sum_{j=1}^n[d|j]\\ &=\sum_{d = 1}^n\varphi(d)\cdot\lfloor\frac{n}{d}\rfloor\lfloor\frac{m}{d}\rfloor \end{align} \] 少处理个前缀和=-=而且我感觉这个做法更好理解
例题:洛谷P2398
和上面那个基本一样 \[ \begin{align} &\sum_{i = 1}^n\sum_{j=i+1}^n\gcd(i,j)\\ &= \sum_{d=1}^nd\sum_{i=1}^n\sum_{j=i+1}^n[gcd(i,j) == d]\\ &=\sum_{d=1}^nd\sum_{i=1}^{\frac{n}{d}}\sum_{j=i+1}^{\frac{n}{d}}[gcd(i,j)==1]\\ &=\sum_{d=1}^nd(\sum_{i=1}^{\frac{n}{d}}\varphi(i) - 1) \end{align} \] (最后一步的化简和上一题大同小异,手画一下就有了)
前置知识: \[ \epsilon(n)=\sum_{d|n}\mu(d)= \begin{cases} 1&n=1\\ 0&otherwise \end{cases} \]
开始愉快地推式子: \[ \begin{align} &\sum_{i=1}^{n}\sum_{i=1}^{m}[\gcd(i,j) == 1] \\ &=\sum_{i=1}^{n}\sum_{i=1}^{m}\epsilon(\gcd(i,j))\\ &=\sum_{i=1}^{n}\sum_{i=1}^{m}\sum_{d|gcd(i,j)}\mu(d)\\ &=\sum_{d = 1}^{\min(n,m)}\mu(d)\sum_{i = 1}^n[d|i]\sum_{j = 1}^{m}[d|j]\\ &=\sum_{d=1}^{\min(n,m)}\mu(d)\lfloor \frac{n}{d}\rfloor \lfloor \frac{m}{d} \rfloor \end{align} \] 同理,我们有: \[ \sum_{i = 1}^n\sum_{j = 1}^n\sum_{k = 1}^n [\gcd(i,j,k) == 1] = \sum_{d = 1}^n \mu(d)(\lfloor \frac{n}{d}\rfloor)^3 \]
\[ \sum a_i \cdot b_j = \frac{\sum a_i^2 - \sum b_j^2}{2} \]
因为\((a+b)^2 = a^2 + b^2 + 2ab\)
\[ \sum_{i = 1}^n i = \frac{n \cdot(n+1)}{2}\\ \sum_{i = 1}^n i^2 = \frac{n \cdot (n+1) \cdot(2n + 1)}{6}\\ \sum_{i = 1}^n i^3 = (\frac{n \cdot (n+1)}{2})^2 \]
\[ \lfloor \frac{a}{bc} \rfloor = \lfloor \frac{\lfloor \frac{a}{b} \rfloor}{c} \rfloor \]
\[ a +b = a\oplus b + 2\cdot a \&b = a|b + a \&b \]
设\(c(n)\)为第\(n\)个Catalan数, 则有\(\sum_{k = 0}^{n-1}c(k)\cdot c(n-1-k) = c(n)\)
区间加平方和(范围\([l,n]\))可通过差分进行. 设差分数组为\(d\), 每次对\([l,n]\)加平方和的操作转换为d[l]++,d[l+1]++
, 然后求d的3阶前缀和即可单点查询.
对于一个\(n\)个点的竞赛图(有向完全图), 不经过重复边的最长路最大为\((n为偶数): \frac{n\cdot (n-1)}{2} - \frac{n}{2} + 1\);\((n为奇数):\frac{n\cdot (n-1)}{2}\); 最小为\(n-1\)
对于一个有 \(n\)个点的无向图,若其中不存在三个点的环,则边数不超过 \(n^2/4\)
给定一个顶点均为整点(坐标为整数的点)的简单多边形,其面积\(A\)和内部格点数\(I\),边上格点数\(B\)的关系是: \(A = I+B/2 - 1\)
一个有\(n\)个节点的完全图有\(n^{n-2}\)种不同的生成树
-仅旋转,染色数和顶点数均为n.欧拉函数优化(抄自Lskkkno1的题解qwq)
1 | ll polya(int n,int k){ |
\[ C_k^k +C_{k+1}^k +...+C_{n}^k(n > k) \]
对于数列\(\{1,2,2,3,3,3,4,4,4,4,...\}\)(数\(i\)出现了\(i\))次, 有通项公式 \[ a_n = \frac{1+2\sqrt{2n}}{2} \]
\[ \sum_{i = 1}^\infin i \cdot \frac{1}{a^i} \]
海伦公式
\(a,b,c\)为三角形边长, \(s = \frac{a+b+c}{2}\)
三角形面积为\(S = \sqrt{s(s-a)(s-b)(s-c)}\)
对于\(C_n^k\), 如果\(n\&k == k\)为奇数, 否则偶数.(按位and)
若\(x\)里有\(k\)个1, 则gen_next(x)
为包含\(k\)个1的下一位数
1 | int gen_next(int x){ |
筛掉\(p^2\)即可. \(p\)是素数且\(p \in [n,m]\)
\(a\)为首项, \(q\)为公比
\[ 求和:S_n = a \cdot \frac{1-q^n}{1-q} (q \ne 1) \]
\[ 求积:P_n = a^n \cdot q^{\frac{n(n-1)}{2}} \]
1 | namespace prime_fac { |
对于集合\(S = \{a_i\},i = 1...n\), 其全部子集中元素和的和为\(2^{n-1}\cdot \sum_{i = 1}^na_i\)
其全部子集中元素积的和为\(\prod_{i = 1}^n(a_i + 1)\)
从中选出大小为\(k\)的全部子集, 元素积的和可用OGF求解, 即 \[ \sum_{S' \in S \and |S'| = k}\space \prod_{p_i\in S'}p_i = [k] \prod_{i = 1}^n (1+a_i) \] NTT分治即可.
给定集合\(m\), 枚举它的全部子集:
1
for(int S = m;S >= 0;S = (S-1)&m)
枚举大小为\(n\)的全部集合\(m\), 且对于每个\(m\), 枚举其全部子集\(S\). 时间复杂度\(O(3^n)\):
1
2
3
4for(int m = 0;m < (1<<n);m++){
for(int S = m;S >= 0; S = (S-1)&m)
}
求解如下问题:
子集和: \[ f[S] = \sum_{T \in S}f[T] \] 超集和: \[ f[T] = \sum_{T\in S}f[S] \]
1 | for(int i = 0; i < n; i++) |
时间复杂度\(O(n \cdot 2^n)\)
跑起来很快, 大概是\(O(log^2(n))\)级别 \[ (2n - 1)!! = \prod_{i = 1,i += 2}^{2n-1}i \]
1 | #include <bits/stdc++.h> |
\[ \sum_{i = 1}^n \sum_{c \in{cnt} \and c < t[i]}\frac{(n - i)!}{(\prod_{c' \in cnt \and c' \neq c}c'!) \cdot (c-1)!} \]
斜率优化可以解决如下问题:
给出一组直线\(y_i = k_i x_ + b_i\). 对于任意\(x\), 求\(y_i\)的极大/极小值.
因为一直要赶一堆莫名其妙的ddl和考试 task3没做 日后再补_(:з」∠)_
调色是增强画面表现力的一种很有效的手段. 本次作业我们处理的颜色在sRGB空间内, 红、绿、蓝三个颜色分别用一个\([0,1]\)的实数来表示. 将三原色分别用一个坐标轴来表示, 便构成了如图所示的色彩空间(图片来自defold.com):
在进行调色时, 我们对渲染结果的每个像素进行处理, 根据像素原本的RGB值将其映射到一个新的色彩空间. 这种映射一般采用LUT(look up table)图来进行. LUT有1D和3D之分, Pilot Engine使用的是3D的LUT, 它看上去像这个样子:
这张图片的长为\(1024\)个像素, 宽为\(32\)个像素, 可以看出它由\(32\)个小色块组成. 将这些小方块自上而下拼接在一起, 便组成了一个\(32 \times 32 \times 32\)的色彩空间. 我们需要做的便是根据每个像素的RGB分量找到它在这张图上对应的像素. 具体来说, 原像素的\(G\)分量指示了新像素在LUT上的纵坐标, \(B\)分量指示了新像素所属的色块编号, \(R\)分量指示了新像素在某个色块内部的横坐标.
我们需要编写的Shader文件是color_grading.frag
, 可以在\engine\shader\glsl\
中找到.
这个shader已经提供了原像素的颜色in_color
和LUT的2D采样器color_grading_lut_texture_sampler
. 我们需要根据原像素的颜色计算出调色后的颜色并将其赋值给out_color
作为输出.
在计算前, 需要获取像素的颜色值:
1 | highp vec4 color = subpassLoad(in_color).rgba; |
颜色值是一个四维向量, 四个维度分别代表R、G、B和alpha, 均为\([0,1]\)的实数.
在计算出坐标后, 需要从LUT上获取对应的颜色:
1 | highp vec4 color_sampled = texture(color_grading_lut_texture_sampler, uv); |
其中color_grading_lut_texture_sampler
已经提供, uv
是一个二维向量, \(u\)代表横坐标, \(v\)代表纵坐标, 坐标原点在图片的左上角, \(x\)轴方向向右, \(y\)轴方向向下. \(u,v\)也是\([0,1]\)上的实数.
我们还需要获取LUT的长宽:
1 | highp ivec2 lut_tex_size = textureSize(color_grading_lut_texture_sampler, 0); |
有了如上函数, 不难计算出每个像素在LUT上的对应位置. 设\(C\)是LUT的宽, \(R,G,B\)分别为像素的原颜色分量, 则有: \[ u = \frac{\lfloor B \cdot C \rfloor + R}{C}\\ v = G \] 为了提高准确度和防止越界, 可以在LUT的边界上各留出0.5个像素, 将RGB值映射到\(C-1\)个像素中去. 具体细节见代码:
1 | #version 310 es |
在\engine\asset\texture\lut
中存放着不同的LUT. 通过修改\engine\asset\global\rendering.global.json
中"color_grading_map"
项可以替换不同的LUT.
在不进行调色时的效果是这样的:
调色后的效果:
要创建自定义的LUT, 只需在GIMP或者PhotoShop等工具编辑原始LUT图像(可以附一张截图方便观察调色效果).
负片效果的LUT:
偏冷色调(好像太绿了):
PS:调色后会出现看上去很奇怪的色块, 目前还不知道如何解决
本文的主要内容是对Dmitry V. Sokolov的tinyrender课程的记录和补充.
我们需要tgaimage.h
来操作TGA文件, model.h
来操作模型. 头文件和实现可以在这里找到:
https://github.com/ssloy/tinyrenderer/tree/f6fecb7ad493264ecd15e230411bfb1cca539a12
可以用如下代码来测试:
1 | #include "tgaimage.h" |
得到一个有红点的TGA文件:
我们需要在二维位图上画一条从\((x_0,y_0)\)到\((x_1,y_1)\)的直线.
直线的两点式为: \[ \frac{y - y_0}{y_1 - y_0} = \frac{x - x_0}{x_1 - x_0} \] 因而对任一点\(x\), 有 \[ y = \frac{x - x_0}{x_1 - x_0} \cdot(y_1 - y_0) + y_0 \] 我们只要枚举\(x\), 计算出每个\(x\)对应的\(y\)即可画出直线. 需要注意的是, 这个算法默认\((x_1,y_1)\)在\((x_0,y_0)\)的右上方, 并且绘制时需要枚举\(\Delta x,\Delta y\)中较大的一方以保证线段不会断开.
代码如下:
1 | void Drawer::Line(int x0, int y0, int x1, int y1, TGAColor color) { |
朴素算法已经能够满足绘图需要, 但是使用了浮点乘法除法, 效率很低.
我们设直线的斜率\(k = \frac{y_1 - y_0}{x_1 - x_0}\), 有 \[ y = (x - x_0) \cdot k + y_0 \] 我们假设当前画笔所在位置的是\((x,y_c)\), 误差为\(E = y- y_c\). 当\(x\)加\(1\)时, \(E\)增加\(k\). 当\(E \ge \frac{1}{2}\)时, 我们需要将\(y_c\)加或减\(1\)同时\(E\)减去\(1\). 这样便避免了每次都要进行的浮点除法运算.
具体来说, 我们只需将不等式的两边乘上\(2 \cdot (x_1 - x_0)\)即可. 此时误差每次增加\(2 \cdot (y_1 - y_0)\). 当\(E \ge (x_1 - x_0)\)时需要将\(y_c\)加或减\(1\)同时\(E\)减去\(2 \cdot (x_1 - x_0)\).
1 | void Drawer::Line(int x0, int y0, int x1, int y1, TGAColor color) { |
我们先用一种十分古老的方法来画一个三角形: 用水平或者竖直的线一行一行地绘制.
在绘制前, 先对三角形的三个顶点按横坐标递增进行排序, 然后标号为\(P_0,P_1,P_2\)(以下默认左下角为坐标原点). 同时将边\(P_0P_1\)标为\(L_0\), \(P_1P_2\)标为\(L_1\), \(P_2P_3\)标为\(L_2\), 如图:
标号后的三角形可按\(x = x_1\)这条直线分割成两部分. 对两部分分别进行绘制即可. 注意处理一下斜率不存在时的细节.
1 | void Drawer::Triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAColor color) { |
设\(V_1,...,V_n\)是\(n\)维向量空间\(V\)中单纯形的顶点, 对于\(V\)中任意一点\(P\), 有 \[ (\sum_{i = 1}^n \lambda_i)\cdot P = \sum_{i = 1}^n \lambda_i \cdot V_i \] 则系数\(\lambda_1,...,\lambda_n\)称为\(P\)关于\(V_1,...V_n\)的重心坐标. 一般规定\(\sum_{i = 1}^n \lambda_i = 1\), 此时称为正规化的重心坐标. 以下讨论的重心坐标均为正规化的.
二维平面上, 单纯形为三角形. 假设给出一三角形\(\Delta ABC\)和三角形所在平面上的一点\(P\), 则\(P\)可被如下重心坐标表示: \[ P = \alpha A + \beta B + \gamma C \] 考虑下图(来源见链接):
将\(\vec{AB},\vec{AC}\)作为坐标系, 可以得到 \[ \begin{align} P =& A + \beta(\vec{AB}) + \gamma(\vec{AC})\\ =& A + \beta(B - A) + \gamma(C - A)\\ =& (1 - \beta - \gamma)A + \beta B + \gamma C \end{align} \] 这样便可以从坐标系的角度来解释重心坐标, 同时能得到 \[ \alpha = 1 - \beta - \gamma \]
将点带入重心坐标的定义, 可以得到: \[ \left \{ \begin{array}{cccc} x_p = (1 - \beta - \gamma) x_a + \beta x_b + \gamma x_c \\ y_p = (1 - \beta - \gamma) y_a + \beta y_b + \gamma y_c \end{array} \right. \] 提取出\(\beta \space \gamma\): \[ \left \{ \begin{array}{cccc} x_p - x_a = \beta(x_b - x_a) + \gamma (x_c - x_a)\\ y_p - y_a = \beta(y_b - y_a) + \gamma (y_c - y_a) \end{array} \right. \] 转化为矩阵形式: \[ \left[ \begin{array}{} x_b - x_a & x_c - x_a\\ y_b - y_a & y_c - y_a \end{array} \right] \left[ \begin{array}{} \beta \\ \gamma \end{array} \right] = \left[ \begin{array}{} x_p - x_a\\ y_p - y_a \end{array} \right] \] 这样便可求解出\(\beta \space \gamma\)进而求出\(\alpha\)
把坐标系的表达式变换一下: \[ \beta \vec{AB} + \gamma \vec{AC} + \vec{PA} = 0 \] 拆开后: \[ \left \{ \begin{array}{cccc} \beta\vec{AB}_x + \gamma \vec{AC}_x + \vec{PA}_x = 0\\ \beta\vec{AB}_y + \gamma \vec{AC}_y + \vec{PA}_y = 0 \end{array} \right. \] 转化为矩阵形式: \[ \left[ \begin{array}{} \beta & \gamma & 1 \end{array} \right] \left[ \begin{array}{} \vec{AB}_x\\ \vec{AC}_x\\ \vec{PA}_x \end{array} \right] = 0\\ \left[ \begin{array}{} \beta & \gamma & 1 \end{array} \right] \left[ \begin{array}{} \vec{AB}_y\\ \vec{AC}_y\\ \vec{PA}_y \end{array} \right] = 0\\ \] 从几何意义考虑, 这相当于\([\beta ,\gamma,1]\)这个向量和后两个向量分别垂直. 因而我们求出后两个向量的叉积(假设结果为\([x,y,z]\)), 则\([\frac{x}{z}, \frac{y}{z},1]\)等于\([\beta,\gamma,1]\).
需要注意\(z = 0\)的特殊情况, 根据叉乘的定义, 此时有\(\vec{AB}_x \cdot \vec{AC}_y - \vec{AB}_y \cdot \vec{AC}_x = 0\), 即\(\vec{AB} \cdot \vec{AC} = 0\), 说明\(ABC\)三点共线. 这时返回一个任意的负重心坐标即可.
如图, 假设三角形面积分别为\(S_A,S_B,S_C\). 则 \[ \alpha = \frac{S_A}{S_A+S_B+S_C}\\ \beta = \frac{S_B}{S_A+S_B+S_C}\\ \gamma = \frac{S_C}{S_A+S_B+S_C} \]
重心坐标有很多应用, 在这里我们能用到的结论是: 若\(\alpha,\beta,\gamma\)均大于\(0\), 则点\(P\)在三角形\(\Delta ABC\)的内部; 若\(\alpha,\beta,\gamma\)有一个等于\(0\), 则点\(P\)在三角形的边上; 若\(\alpha,\beta,\gamma\)有两个等于\(0\), 则点\(P\)在三角形的顶点上.
在绘图时, 用一个最小的矩形包围住三角形, 再枚举并判断每个点是否在三角形内部即可. 下面的代码采用了向量叉积法:
1 | Vec3f Drawer::Barycentric(const Vec2i (&vertex)[3],const Vec2i & P){ |
使用tinyrender提供的模型来测试一下我们的三角形绘制程序(先给每个面一个随机颜色):
加上一点阴影并剔除背面的三角形:
现在我们需要考虑剔除掉不能被我们看见的像素(Step 2通过Back-face culling剔除了一些面, 但是仍然不正确, 例如嘴部整个消失了). z-buffer的思想是维护一个像素点在待渲染平面上的一个距离buffer, 每次用离相机更近的点来剔除已经存在的点. z-buffer的实现非常简单, 下面主要讨论如何求每个点距相机的距离.
首先考虑一维的情形. 假设我们要绘制从\(A(x_0,y_0)\)到\(B(x_1,y_1)\)的一条线段, 当前绘制到点\(P(x,y)\), 摄像机的视角与\(y\)轴的反方向相同:
在绘制时我们要通过\(x\)坐标计算出\(y\)来. 不难得到 \[ \frac{y - y_0}{y1 - y_0} = \frac{x - x_0}{x_1 - x_0} \] 我们设\(\beta = \frac{x - x_0}{x_1 - x_0}\), 则有 \[ y = (1 - \beta)y_0 + \beta y_1 \] 是不是很熟悉? \(\alpha = 1 - \beta\)和\(\beta\)就是\(P\)关于线段\(AB\)的重心坐标(一维单纯形是线段). 我们将其推广到二维, 便能求出三角形每个点距离相机的距离\(z\): \[ z = \alpha z_0+ \beta z_1 + \gamma z_2 \]
1 | void Drawer::Triangle(const Vec3i (&vertex)[3],const TGAColor & color,double * zbuffer) { |
效果如下:
这一部分tinyrender讲得很含糊而且不直观, 所以直接按照GAMES101的内容去实现. 本节只讲述实现. obj文件格式可以在https://en.wikipedia.org/wiki/Wavefront_.obj_file找到.
透视投影的实现似乎有问题, 先鸽了
写的不是很满意, 也鸽了
感觉这场F比E简单多了= =
给出一个\(n \times m\)的数组 \((1 \leq n \leq 10^5,1 \leq m \leq 5)\). 数组的每一行元素均互不相同, 且每一行有一个权值\(w\) \((1 \leq w \leq 10^9)\).
你需要找出不同的两行, 使得这两行在不存在重复元素的同时权值最小. 输出权值, 无解输出-1
.
暴力做需要\(O(n^2m)\).
将行按权值排序, 把每一个元素对应的行用bitset存下来, 0代表非法1代表合法. 再在枚举每行的时候找到第一个不冲突的行更新答案就可以做到\(O(\frac{n^2m}{w})\), 其中\(w = 32\)或\(64\). 但是这样需要\(O(\frac{n^2m}{8})\)的空间, 会MLE.
考虑分块. 设块大小为\(B\), 将每个元素按出现次数分类:
(感觉复杂度算的很不对劲啊- - 反正能过)
然后B选一个差不多的大小比如\(\sqrt{nm}\)就能过了.
1 | // |
中规中矩的字符串题.
给出一个字符串\(s\), 你可以选取\(s\)的任意子串将其翻转得到\(s'\), 该操作最多进行一次. 求字典序最小的\(s'\).
\(|s| \leq 10^5, \sum |s| \leq 1.5 \cdot 10 ^ 6\)
以下字符串的下标均从\(0\)开始. \(lcp(s,t)\)代表\(s\)和\(t\)的最长公共前缀. \(rev(s)\)表示将\(s\)翻转后的字符串.
设\(n = |s|\), 朴素的暴力需要至少\(O(n^2)\)来枚举翻转子串的两个端点\(L,R\). 我们需要想办法固定住其中的一个端点.
首先尝试放开限制, 把翻转变成任意排列. 这样将\(s\)排序后得到的\(s'\)一定是最优的. 记这样的\(s'\)为\(t\).
回到原题, 容易看出, \(s\)和\(t\)的最长公共前缀部分无论我们如何操作都不可能再让字典序变小, 而在这之后的部分是有可能减小字典序的. 且因为字典序的性质(比较第一个不相同的字符), 我们必须尝试让\(s'\)和\(t\)不同的第一个字符最小. 因此\(L = lcp(s,t)\).
设当前的最优答案为\(ans\), 在固定了\(L\)之后, 只需\(O(n)\)枚举\(R\), 并快速比较\(ans\)和\(s'\)的大小即可. 这里可以用\(lcp\)来比较两个字符串的大小.
设需要比较\(A = s[a...b]\)和\(B = s[c...d]\)的关系. 若\(lcp(a,c) \ge \min(|A|,|B|)\),则\(A < B\)等价于\(|A| < |B|\) 否则, \(A < B\)等价于\(rank[a] < rank[c]\)
1 | int CompString(int s1,int len1,int s2,int len2){ |
注意: 可以只用\(lcp\)来比较字符串大小(这样就可以用hash等方法来求\(lcp\)而不用写SA). 只要把上面的第6行替换为
1 | else return s[s1 + tlcp] < s[s2 + tlcp]?-1:(s[s1 + tlcp] == s[s2 + tlcp]?0:1); |
即可.
首先将整个翻转后的\(s\)拼接在原字符串后, 中间用特殊字符隔开, 例如u = s + "$" + rev(s)
. 再使用后缀数组处理\(u\). 这样在原串中位置为\(i\)的字符, 在反串中的位置就是\(2*n - i\).
假设当前枚举到\(R\), 最优答案为\(R'\), 如下图:
我们需要比较的是\(s[L,R'-1] + rev(s[R',R])\)和\(rev(s[L,R])\)的大小, 如果后者更小则更新答案. 为了方便比较, 可以将每段字符划分成AB两段:
现在需要比较的就是\(rev(A_1) + B_1\)和\(rev(A_2) + rev(B_2)\)的大小. 由于A, B长度分别相等, 可以用part2的方法分别比较\(rev(A_1)\)与\(rev(A_2)\), \(B_1\)与\(rev(B_2)\)的大小
每段的(起始位置,长度)可以通过简单推导得到:
\(rev(A_1)\): \((2*n - R',R' - L + 1)\)
\(rev(A2)\): \((2*n - R,R - L + 1)\)
\(B_1\): \((R' + 1,i - R')\)
\(rev(B_2)\): \((2*n - R + R' - L + 1,i - R')\)
其他细节见代码. 时间复杂度\(O(n \log n)\). jls好像有\(O(n)\)做法, 有没有好哥哥教教QAQ
1 | // |