Algorithm Review 4 动态规划 计算几何

动态规划

  • 通过发掘转移时最优解的必要条件往往能使抽象的转移条件变得简单。
  • 当问题有多维的限制时,将其中一维排序往往能简化状态设计和转移。

矩阵快速幂优化 DP

  • 以下面的顺序计算矩阵乘法,访问内存连续,效率最高。
	friend inline matrix operator * (const matrix &a, const matrix &b)
	{
		matrix c;
		c.Clear(a.gn, b.gm);
		for (int i = 0; i < a.gn; ++i)
			for (int k = 0; k < a.gm; ++k)
			{
				int s = a.g[i][k];
				for (int j = 0; j < b.gm; ++j)
					c.g[i][j] = (1ll * s * b.g[k][j] + c.g[i][j]) % mod;
			}		
		return c;
	}
  • 定义广义矩阵乘法 C i , j = ⨁ k = 1 n A i , k ⊗ B k , j C_{i,j} = \bigoplus\limits_{k=1}^{n} A_{i,k}\otimes B_{k,j} Ci,j=k=1nAi,kBk,j只需满足 ⊗ \otimes 具有结合律, ⊕ \oplus 具有交换律,且 ⊗ \otimes ⊕ \oplus 具有分配律, 矩阵乘法就存在结合律。

证明 假设有矩阵 D = A B C D = ABC D=ABC,需证明 D = A ( B C ) D = A(BC) D=A(BC),即
D i , j = ⨁ l = 1 n ( A B ) i , l ⊗ C l , j = ⨁ l = 1 n ( ⨁ k = 1 n A i , k ⊗ B k , l ) ⊗ C l , j = ⨁ l = 1 n ⨁ k = 1 n A i , k ⊗ B k , l ⊗ C l , j = ⨁ k = 1 n ⨁ l = 1 n A i , k ⊗ B k , l ⊗ C l , j = ⨁ k = 1 n ⨁ l = 1 n A i , k ⊗ ( B k , l ⊗ C l , j ) = ⨁ k = 1 n A i , k ⊗ ( ⨁ l = 1 n B k , l ⊗ C l , j ) = ⨁ k = 1 n A i , k ⊗ ( B C ) k , j \begin{aligned} D_{i,j} &= \bigoplus\limits_{l=1}^{n} (AB)_{i,l}\otimes C_{l,j} \\ &= \bigoplus\limits_{l=1}^{n} \left(\bigoplus\limits_{k=1}^{n} A_{i,k}\otimes B_{k,l}\right)\otimes C_{l,j} \\&= \bigoplus\limits_{l=1}^{n} \bigoplus\limits_{k=1}^{n} A_{i,k}\otimes B_{k,l}\otimes C_{l,j}\\&= \bigoplus\limits_{k=1}^{n} \bigoplus\limits_{l=1}^{n} A_{i,k}\otimes B_{k,l}\otimes C_{l,j}\\&= \bigoplus\limits_{k=1}^{n} \bigoplus\limits_{l=1}^{n} A_{i,k}\otimes \left(B_{k,l}\otimes C_{l,j}\right)\\&= \bigoplus\limits_{k=1}^{n} A_{i,k}\otimes \left(\bigoplus\limits_{l=1}^{n}B_{k,l}\otimes C_{l,j}\right)\\&= \bigoplus\limits_{k=1}^{n} A_{i,k}\otimes(BC)_{k,j} \end{aligned} Di,j=l=1n(AB)i,lCl,j=l=1n(k=1nAi,kBk,l)Cl,j=l=1nk=1nAi,kBk,lCl,j=k=1nl=1nAi,kBk,lCl,j=k=1nl=1nAi,k(Bk,lCl,j)=k=1nAi,k(l=1nBk,lCl,j)=k=1nAi,k(BC)k,j

单调队列优化 DP

  • 单调队列/栈上结点也可以记录信息,如前缀和等,或在插入或删除的过程中用数据结构维护。

典例 POJ3017

  • f i f_i fi 表示将前 i i i 个数分成若干段、满足每段所有数的和不超过 M M M 时各段最大值之和的最小值。不难得到转移:
    f i = min ⁡ 0 ≤ j < i 且 ∑ k = j + 1 i a k ≤ M { f j + max ⁡ j + 1 ≤ k ≤ i { a k } } f_i = \min\limits_{0\le j < i且\sum\limits_{k =j+1}^{i}a_k\le M}\{f_j + \max\limits_{j + 1\le k\le i}\{a_k\}\} fi=0j<ik=j+1iakMmin{fj+j+1kimax{ak}}
  • 因为在此题的限制条件中 f i f_i fi 是单调不降的,容易证明,最优解需满足以下两个条件之一:
    • a j = max ⁡ j ≤ k ≤ i { a k } a_j = \max\limits_{j\le k\le i}\{a_k\} aj=jkimax{ak}
    • ∑ k = j i a k > M \sum \limits_{k = j}^{i}a_k > M k=jiak>M
  • 第一个条件可以维护一个决策点 j j j 单调递增、数值 a j a_j aj 单调递减的队列,并用数据结构维护最优转移,这里使用的是懒惰删除的二叉堆,第二个条件只需要维护单个指针单独转移即可。
	l = 0, ql = 1, qr = 0;
	for (int i = 1; i <= n; ++i)
	{	
		while (l < i && sum[i] - sum[l] > M)
			++l;
		while (ql <= qr && sum[i] - sum[que[ql]] > M)
			val[que[ql++]] = -1;
		while (ql <= qr && a[que[qr]] <= a[i])
			val[que[--qr]] = -1;
		if (ql <= qr)
			q.push(point(val[que[qr]] = f[que[qr]] + a[i], que[qr]));
		que[++qr] = i;
		f[i] = f[l] + a[que[ql]];
		while (!q.empty() && val[q.top().t] != q.top().s)
			q.pop();
		if (!q.empty())
			CkMin(f[i], q.top().s);
	}

多重背包

  • 给定 n n n 种物品,其中第 i i i 种物品的体积为 v i v_i vi,价值为 w i w_i wi,有 c i c_i ci 个。
  • 求能够放入容积 m m m 的背包的物品的最大价值总和。

二进制拆分法

  • 求出 ∑ k = 0 p 2 k ≤ c i \sum \limits_{k = 0}^{p}2^k\le c_i k=0p2kci 最大的 p p p,令 r i = c i − ∑ k = 0 p 2 k r_i = c_i - \sum\limits_{k = 0}^{p}2^k ri=cik=0p2k
  • 将数量为 c i c_i ci 的第 i i i 种物品拆成 p + 2 p + 2 p+2 种物品,其体积分别为:
    2 0 v i , 2 1 v i , … , 2 p v i , r i v i 2^0v_i, 2^1v_i,\dots,2^pv_i,r_iv_i 20vi,21vi,,2pvi,rivi
  • 时间复杂度 O ( n m log ⁡ c ) \mathcal O(nm\log c) O(nmlogc),其中 c = max ⁡ 1 ≤ i ≤ n { c i } c = \max\limits_{1\le i\le n}\{c_i\} c=1inmax{ci}

单调队列优化

  • 将容积那一维按照模 v i v_i vi 的余数分类,则可利用单调队列优化。
  • 时间复杂度 O ( n m ) \mathcal O(nm) O(nm)

斜率优化 DP

  • 形如下式的转移宜采用斜率优化:
    f i = max ⁡ / min ⁡ { f j + A ( i ) B ( j ) + C ( i ) + D ( j ) } f_i = \max/\min\{f_j + A(i)B(j) + C(i)+D(j)\} fi=max/min{fj+A(i)B(j)+C(i)+D(j)}
    其中 A , B , C , D A,B,C,D A,B,C,D 分别为含对应变量的多项式, A , B A,B A,B 中的最高次数为一次。
  • 去除 max ⁡ / min ⁡ \max/\min max/min 的限制,移项得到:
    − A ( i ) B ( j ) + f i − C ( i ) = f j + D ( j ) -A(i)B(j) + f_i - C(i) = f_j + D(j) A(i)B(j)+fiC(i)=fj+D(j)
    将每个可能的决策 ( B ( j ) , f j + D ( j ) ) (B(j), f_j + D(j)) (B(j),fj+D(j)) 视作平面上的一点,最优决策即用固定斜率 − A ( i ) -A(i) A(i) 的直线去截这些点,最大化/最小化截距,因而只需要维护这些点的上凸壳/下凸壳。
  • 维护的方式视具体情况而定:
    • A ( i ) , B ( j ) A(i),B(j) A(i),B(j) 均单调,用单调队列/单调栈维护即可。
    • A ( i ) , B ( j ) A(i), B(j) A(i),B(j) 其中一个单调,将单调的那个变量视作决策点(若只有 A ( i ) A(i) A(i) 单调则需要倒着转移),求最优决策时在凸壳上二分即可。
    • A ( i ) , B ( j ) A(i),B(j) A(i),B(j) 均不单调,则需要用平衡树维护凸壳或离线后 CDQ \text{CDQ} CDQ分治。
  • 尽量不要用实数判斜率,同时要注意横坐标相同的情况要特殊处理,视题目的要求只保留纵坐标最大/最小的点即可。
  • 以下为平衡树 Splay \text{Splay} Splay 维护下凸壳的模板,利用了平衡树上二分和 Splay 操作,减少了部分常数。
namespace Hull
{
	const int N = 1e5 + 5;
	int rt; 
	int fa[N], lc[N], rc[N];
	int suf[N], pre[N]; 
	ll valx[N], valy[N];

	inline void Init(int x, ll vx, ll vy)
	{
		valx[x] = vx;
		valy[x] = vy;	
	}
	
	inline bool Slope1(int x, int y, int z)
	{
		if (!x || !y || !z)
			return true;
		return (valy[y] - valy[x]) * (valx[z] - valx[y])
		    <= (valy[z] - valy[y]) * (valx[y] - valx[x]);
	}
	
	inline bool Slope2(int x, int y, ll z)
	{
		if (!x || !y) 
			return true;
		return (valy[y] - valy[x]) <= z * (valx[y] - valx[x]);
	}
	
	inline void Rotate(int x)
	{
		int y = fa[x], z = fa[y];
		bool flag = lc[y] == x;
		int b = flag ? rc[x] : lc[x];
		fa[x] = z, fa[y] = x;
		b ? fa[b] = y : 0;
		z ? (lc[z] == y ? lc[z] : rc[z]) = x : 0;
		flag ? (rc[x] = y, lc[y] = b) : (lc[x] = y, rc[y] = b);	
	}	

	inline bool whichSide(int x)
	{
		return rc[fa[x]] == x;	
	}	

	inline void Splay(int x, int tar)
	{
		while (fa[x] != tar)
		{
			if (fa[fa[x]] != tar)
				Rotate(whichSide(fa[x]) == whichSide(x) ? fa[x] : x);
			Rotate(x); 
		}
		!tar ? rt = x : 0;
	}

	inline int findLeft(int x, int y)
	{
		int res = x;
		while (x)
		{
			if (Slope1(pre[x], x, y)) 
				res = x, x = rc[x];
			else 
				x = lc[x];
		}
		return res;
	}

	inline int findRight(int x, int y)
	{
		int res = x;
		while (x)
		{
			if (Slope1(y, x, suf[x])) 
				res = x, x = lc[x];
			else 
				x = rc[x];
		}
		return res;
	}
 	
 	inline void Clear(int &x)
	{
		if (!x)
			return ;
		Clear(lc[x]);
		Clear(rc[x]);
		fa[x] = pre[x] = suf[x] = 0;
		x = 0;
	}
	
	inline void Insert(int id)
	{
		int x = rt, y = 0, dir;
		while (x)
		{
			y = x;
			if (valx[id] < valx[x]) 
				x = lc[x], dir = 0;
			else 
				x = rc[x], dir = 1;
		}
		fa[x = id] = y;
		if (y) (dir ? rc[y] : lc[y]) = x;
		Splay(x, 0); 
		if (lc[x])
		{
			int z = findLeft(lc[x], x);
			Splay(z, x); 
			Clear(rc[z]);
			suf[z] = x;
			pre[x] = z;
		}
		if (rc[x])
		{
			int z = findRight(rc[x], x);
			Splay(z, x); 
			Clear(lc[z]);
			pre[z] = x;
			suf[x] = z;
		}
		if (!Slope1(pre[x], x, suf[x]))
		{
			rt = lc[x]; 
			rc[rt] = rc[x];
			fa[rc[x]] = rt; 
			fa[rt] = 0;
			lc[x] = rc[x] = fa[x] = 0;
			suf[rt] = rc[rt];
			pre[rc[rt]] = rt;
		}
	}

	inline int Query(ll z)
	{
		int x = rt, res = 0;
		while (x)
		{
			if (Slope2(pre[x], x, z)) 
				res = x, x = rc[x];
			else 	
				x = lc[x];
		}
		return Splay(res, 0), res;
	}	
}
  • 还可以换一种表示形式:

f i = max ⁡ / min ⁡ { B ( j ) A ( i ) + D ( j ) + f j } + C ( i ) f_i = \max /\min \{B(j)A(i) + D(j) + f_j\} + C(i) fi=max/min{B(j)A(i)+D(j)+fj}+C(i)

  • max ⁡ / min ⁡ \max / \min max/min 内部的式子可以看作是若干条以 B ( j ) B(j) B(j) 为斜率、 D ( j ) + f j D(j) + f_j D(j)+fj 为截距的直线, 即可用李超线段树维护,相较于平衡树的写法要简单很多,代码见数据结构部分。
  • 若采用动态开点的写法,还可支持线段树合并,因而还可支持子树形式的转移。

决策单调性

四边形不等式

  • w ( x , y ) w(x,y) w(x,y) 为定义在整数集合上的二元函数,对于定义域上的任意整数 a , b , c , d a,b,c,d a,b,c,d,其中 a ≤ b ≤ c ≤ d a\le b \le c\le d abcd,都有 w ( a , d ) + w ( b , c ) ≥ w ( a , c ) + w ( b , d ) w(a,d) + w(b,c) \ge w(a,c) + w(b,d) w(a,d)+w(b,c)w(a,c)+w(b,d),称为函数 w w w 满足四边形不等式。

一维决策单调性

  • 在状态转移方程 f i = min ⁡ 0 ≤ j < i { f j + w ( j , i ) } f_i = \min\limits_{0 \le j < i}\{f_j + w(j,i)\} fi=0j<imin{fj+w(j,i)} 中,若函数 w w w 满足四边形不等式,则 f f f 具有决策单调性。
  • 用队列维护若干个三元组 ( l , r , j ) (l,r,j) (l,r,j),表示 [ l , r ] [l,r] [l,r] 内的最优决策均为 j j j
  • 在队尾插入时:
    • 若比前一个三元组整个区间内的决策都优,则直接合并,继续检查前一个三元组。
    • 若比前一个三元组整个区间内的决策都不优,则直接插入队尾。
    • 否则在区间上二分找到分界点,修改两个区间的分界后插入队尾。

二维决策单调性

  • 在状态转移方程 f i , j = min ⁡ i ≤ k < j { f i , k + f k + 1 , j + w ( i , j ) } f_{i,j} = \min\limits_{i \le k < j}\{f_{i,k}+f_{k+1,j}+w(i,j)\} fi,j=ik<jmin{fi,k+fk+1,j+w(i,j)} 中,若下面三个条件成立:
    • 可规定 f i , i = w ( i , i ) = 0 f_{i,i} = w(i,i) = 0 fi,i=w(i,i)=0
    • w w w 满足四边形不等式。
    • 对于任意的 a ≤ b ≤ c ≤ d a \le b \le c \le d abcd,有 w ( a , d ) ≥ w ( b , c ) w(a,d) \ge w(b,c) w(a,d)w(b,c)
  • f f f 也满足四边形不等式,设 p i , j p_{i,j} pi,j 表示令 f i , j f_{i,j} fi,j 取到最小值的 k k k 值,则恒有 p i , j − 1 ≤ p i , j ≤ p i + 1 , j p_{i,j-1}\le p_{i,j}\le p_{i + 1,j} pi,j1pi,jpi+1,j
  • 因此我们在转移时只需枚举 [ p i , j − 1 , p i + 1 , j ] [p_{i,j-1},p_{i+1,j}] [pi,j1,pi+1,j] 内的 k k k,对于长度为 L + 1 L+1 L+1 的区间,总枚举量为:
    ( p 2 , L + 1 − p 1 , L ) + ( p 3 , L + 2 − p 2 , L + 1 ) + ⋯ + ( p n − L + 1 , n − p n − L , n − 1 ) = p n − L + 1 , n − p 1 , L (p_{2, L+1} - p_{1,L})+(p_{3,L+2}-p_{2,L+1})+\dots+(p_{n - L + 1, n} - p_{n - L, n - 1}) = p_{n - L + 1, n} - p_{1,L} (p2,L+1p1,L)+(p3,L+2p2,L+1)++(pnL+1,npnL,n1)=pnL+1,np1,L
  • 因此总的时间复杂度为 O ( ∑ i = 1 n − 1 ( p n − i + 1 , n − p 1 , i ) ) = O ( n 2 ) \mathcal O(\sum \limits_{i = 1}^{n - 1}(p_{n-i+1,n} - p_{1,i})) = \mathcal O(n^2) O(i=1n1(pni+1,np1,i))=O(n2)
  • 形如 f i , j = max ⁡ i ≤ k ≤ j / min ⁡ i ≤ k ≤ j { f i , k + f k , j + C } , C ∈ R f_{i,j} = \max\limits_{i \le k \le j}/\min\limits_{i \le k \le j}\{f_{i,k} + f_{k,j}+C\}, C \in \mathbb R fi,j=ikjmax/ikjmin{fi,k+fk,j+C},CR 也可以套用上述流程优化。

GarsiaWachs 算法

  • 上述二维决策单调性能解决的石子合并问题有一种专门的非动态规划算法。
  • 设第 i i i 堆石子的数目为 a i a_i ai,令 a 0 = a n + 1 = + ∞ a_0 = a_{n + 1} = +\infin a0=an+1=+,具体步骤如下:
    • 找到满足 a k − 1 < a k + 1 a_{k - 1} < a_{k + 1} ak1<ak+1 最大的 k k k
    • 找到满足 a j > a k − 1 + a k a_j > a_{k - 1} + a_k aj>ak1+ak j < k j < k j<k 的最大的 j j j
    • 删除 a k − 1 , a k a_{k - 1}, a_k ak1,ak,在 a j a_j aj 后插入 a k + a k − 1 a_k + a_{k - 1} ak+ak1
    • 重复上述过程直至石子被合并为一堆。
  • 空间复杂度 O ( n ) \mathcal O(n) O(n),时间复杂度 O ( n 2 ) \mathcal O(n^2) O(n2),可用平衡树优化至 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

证明 待补充。

	read(n);
	v.push_back(Maxn);
	for (int i = 1, x; i <= n; ++i)
	{
		read(x);
		v.push_back(x);
	}
	v.push_back(Maxn);
	while (n > 1)
	{
		int j, k;
		for (k = 1; k <= n; ++k)
			if (v[k - 1] < v[k + 1])
				break ;
		for (j = k - 1; j >= 0; --j)
			if (v[j] > v[k - 1] + v[k])
				break ;
		int sum = v[k - 1] + v[k];
		v.erase(v.begin() + k - 1);
		v.erase(v.begin() + k - 1);	
		v.insert(v.begin() + j + 1, sum);
		ans += sum;
		--n;
	}
	printf("%d\n", ans);

计算几何

基础操作

  • 判断点 A A A 在线段 P Q PQ PQ 即判断 ∣ A P ∣ + ∣ A Q ∣ = ∣ P Q ∣ |AP| + |AQ| = |PQ| AP+AQ=PQ
  • 向量的旋转 将向量 a ⃗ = ( x , y ) \vec{a} = (x,y) a =(x,y) 逆时针旋转 θ \theta θ 得到向量 b ⃗ = ( x cos ⁡ θ − y sin ⁡ θ , x sin ⁡ θ + y cos ⁡ θ ) \vec{b} = (x\cos \theta - y\sin \theta, x\sin \theta + y\cos\theta) b =(xcosθysinθ,xsinθ+ycosθ),由三角函数的和角公式可证。
  • 曼哈顿距离与切比雪夫距离
    • 二维曼哈顿距离为 ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ |x_1 - x_2| + |y_1 - y_2| x1x2+y1y2,切比雪夫距离为 max ⁡ { ∣ x 1 − x 2 ∣ , ∣ y 1 − y 2 ∣ } \max\{|x_1 - x_2|,|y_1 - y_2|\} max{x1x2,y1y2}
    • ( x , y ) (x,y) (x,y) 坐标下的曼哈顿距离,等同于 ( x + y , x − y ) (x + y, x - y) (x+y,xy) 坐标下的切比雪夫距离。
    • ( x , y ) (x, y) (x,y) 坐标下的切比雪夫距离,等同于 ( x + y 2 , x − y 2 ) (\frac{x + y}{2},\frac{x - y}{2}) (2x+y,2xy) 坐标下的曼哈顿距离。

欧拉公式

  • 在任何一个规则球面地图上,定义 R R R 为区域个数, V V V 为顶点个数, E E E 为边界个数,则恒有:
    R + V − E = 2 R+V-E=2 R+VE=2
  • 推论 凸正多面体(柏拉图立体)有且仅有 5 种。

证明 取凸正多面体一个顶点,设其向外有 n ( n ≥ 3 ) n(n\ge 3) n(n3) 条棱,再取凸正多面体一个面,设其为正 m ( m ≥ 3 ) m(m\ge 3) m(m3) 边形。则
n V = 2 E ⇔ V = 2 E n m R = 2 E ⇔ R = 2 E m nV=2E \Leftrightarrow V = \dfrac{2E}{n}\\ mR=2E \Leftrightarrow R = \dfrac{2E}{m}\\ nV=2EV=n2EmR=2ER=m2E
带入欧拉公式,得
1 m + 1 n = 1 E + 1 2 \dfrac{1}{m}+\dfrac{1}{n}=\dfrac{1}{E}+\dfrac{1}{2} m1+n1=E1+21
注意到 m , n m,n m,n 不能同时大于 3,且其中一个等于 3 时另一个不能超过 5。
符合条件的 m , n m,n m,n 的解只有五组,如下图所示。Algorithm Review 4 动态规划 计算几何_第1张图片

  • 设棱长为 a a a,五种凸正多面体的各项数据如下(棱切球即过各边中点的球)。
正四面体 立方体 正八面体 正十二面体 正二十面体
R R R 4 6 8 12 20
V V V 4 8 6 20 12
E E E 6 12 12 30 30
内切球半径 6 12 a \frac{\sqrt 6}{12}a 126 a 1 2 a \frac{1}{2}a 21a 6 6 a \frac{\sqrt 6}{6}a 66 a 1 2 5 2 + 11 10 5 a \frac{1}{2}\sqrt{\frac{5}{2}+\frac{11}{10}\sqrt 5}a 2125+10115 a 3 3 + 15 12 a \frac{3\sqrt 3 + \sqrt {15}}{12}a 1233 +15 a
外接球半径 6 4 a \frac{\sqrt 6}{4}a 46 a 3 2 a \frac{\sqrt 3}{2}a 23 a 2 2 a \frac{\sqrt 2}{2}a 22 a 3 4 ( 1 + 5 ) a \frac{\sqrt 3}{4}(1+\sqrt 5)a 43 (1+5 )a 10 + 2 5 4 a \frac{\sqrt{10 + 2\sqrt 5}}{4}a 410+25 a
棱切球半径 2 4 a \frac{\sqrt 2}{4}a 42 a 2 2 a \frac{\sqrt 2}{2}a 22 a 1 2 a \frac{1}{2}a 21a 3 + 5 4 a \frac{3 + \sqrt 5}{4}a 43+5 a 1 + 5 4 a \frac{1+\sqrt 5}{4}a 41+5 a

Pick 定理

  • 定义 对于一个所有顶点均为整点的简单多边形,定义 S S S 为这个多边形的面积, i i i 为严格在这个多边形内部的格点数, b b b 为在这个多边形边上的格点数,则三者满足关系式 S = i + b 2 − 1 S = i + \frac{b}{2} - 1 S=i+2b1

证明 考虑证明 引理1引理3,则 Pick 定理显然成立。

  • 引理1 若两个只有一条公共边的多边形满足 Pick 定理,则将两个多边形去掉公共边,合并成的一个多边形也满足 Pick 定理。

证明 设合并的两个多边形为 P , Q P,Q P,Q,它们的公共边上的格点数(不包括端点)为 c c c
S = S P + S Q = i P + i Q − b P + b Q 2 − 2 = i − c + b − 2 c − 2 2 − 2 = i + b 2 − 1 \begin{aligned}S &= S_P + S_Q \\&=i_P + i_Q - \frac{b_P + b_Q}{2} - 2\\&= i - c + \frac{b - 2c - 2}{2} - 2\\& =i + \frac{b}{2} - 1 \\\end{aligned} S=SP+SQ=iP+iQ2bP+bQ2=ic+2b2c22=i+2b1

  • 引理2 有两个只有一条公共边的多边形,若将这两个多边形去掉公共边,合并成的一个多边形满足 Pick 定理,且这两个多边形中有一个满足 Pick 定理,则另一个多边形也满足 Pick 定理。

证明 用类似 引理1 的方法即可。

  • 引理3 任意一个三角形都满足 Pick 定理。

证明

  1. 已知面积为 1 的正方形满足 Pick 定理,由 引理1 得任意大小的矩形都满足 Pick 定理。

  2. 将一个矩形拆分为两个全等的直角三角形,证明任意一个直角三角形都满足 Pick 定理。

    证明 设两个直角三角形公共边上的格点数(不包括端点)为 c c c,原矩形为 R R R,拆分出的直角三角形为 T T T
    S T = S R 2 = i R 2 + b R 4 − 1 2 = 2 i T + c 2 + 2 b T − 2 c − 2 4 − 1 2 = i T + b T 2 − 1 \begin{aligned}S_T &= \frac{S_R}{2} \\ &= \frac{i_R}{2} + \frac{b_R}{4} - \frac{1}{2} \\&= \frac{2i_T + c}{2} + \frac{2b_T - 2c - 2}{4} - \frac{1}{2} \\&= i_T + \frac{b_T}{2} - 1\\\end{aligned} ST=2SR=2iR+4bR21=22iT+c+42bT2c221=iT+2bT1

  3. 任意一个三角形显然可以由一个矩形拆去不多于3个的直角三角形得到,由 引理2 可知得证。

凸包

  • 常见的求法为 Graham \text{Graham} Graham 扫描法,先找出最左下角的点,将其余点按照相对于该点的极角排序(实现时不需要真的求出极角,只需要通过叉积判断即可),然后维护一个栈,将点按照极角序加入该栈,同样通过叉积判断,若不满足凸包的形态则弹栈,最后即能求出凸包。
#include 
 
typedef long long ll;
typedef long double ld;
const int N = 1e5 + 5;
const ld eps = 1e-8;
int n, top, pos[N]; 
ld ans;

struct point
{
	ld x, y;
	
	point() {}
	point(ld X, ld Y):
		x(X), y(Y) {}
	
	inline void scan()
	{
		double _x, _y;
		scanf("%lf%lf", &_x, &_y);
		x = _x, y = _y;
	}
	
	inline bool operator < (const point &a) const 
	{
		return x < a.x || fabsl(x - a.x) <= eps && y < a.y;
	}
	
	inline ld dist() const 
	{
		return x * x + y * y;
	}
	
	inline point operator - (const point &a) const
	{
		return point(x - a.x, y - a.y);
	}
	
	inline ld operator * (const point &a) const
	{
		return x * a.y - y * a.x;
	}
}p[N], stk[N];

inline bool cmp(const int &x, const int &y)
{
	ld del = p[x] * p[y];
	if (fabsl(del) <= eps)
		return p[x].dist() < p[y].dist();
	else 
		return del > 0;
}

int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i)
		p[i].scan();
		
	int id = 1;
	for (int i = 2; i <= n; ++i)
		if (p[i] < p[id])
			id = i;
	if (id != 1) 
		std::swap(p[id], p[1]);	
	for (int i = n; i >= 1; --i)
		pos[i] = i, p[i] = p[i] - p[1];
	
	std::sort(pos + 2, pos + n + 1, cmp);
	
	stk[top = 1] = p[1];
	for (int i = 2; i <= n; ++i)
	{
		int j = pos[i];
		while (top > 1 && (p[j] - stk[top - 1]) * (stk[top] - stk[top - 1]) >= -eps) --top;
		stk[++top] = p[j];
	}
	
	for (int i = 1; i < top; ++i)
		ans += sqrtl((stk[i] - stk[i + 1]).dist());
	ans += sqrtl((stk[top] - stk[1]).dist());
	
	printf("%.2lf\n", (double)ans);
}

你可能感兴趣的:(学习笔记,动态规划,计算几何)