算法导论

说明

对于算法基础的一个补充

矩形求交并

等效到线段的交并上,注意这里把矩形表示调整为左下角 + 右上角

static const rectangle empty_tuple = make_tuple(INT64_MAX,INT64_MAX,INT64_MAX,INT64_MAX);
tuple<ll,ll,ll,ll> get_insec(tuple<ll,ll,ll,ll> a,tuple<ll,ll,ll,ll> b)
  {
  	if (a == empty_tuple or b == empty_tuple)
  	  return empty_tuple;
  	ll x1,y1,x2,y2;
	ll x3,y3,x4,y4;
	std::tie(x1,y1,x2,y2) = a;
	std::tie(x3,y3,x4,y4) = b;
	if (x1 > x2)
	  swap(x1,x2);
	if (y1 > y2)
	  swap(y1,y2);
	if (x3 > x4)
	  swap(x3,x4);
	if (y3 > y4)
	  swap(y3,y4);
	if (x4 < x1 or x2 < x3)
	  return empty_tuple;
	if (y4 < y1 or y2 < y3)
	  return empty_tuple;
	return make_tuple(max(x1,x3),max(y1,y3),min(x2,x4),min(y2,y4));	
  }

RSA加密

前置知识
加密原理

欧拉定理
\(gcd(a,n) = 1\),那么
\(a^{\phi(n)} = 1 (mod n)\)


RSA算法
任选两个不同的大质数p = 61,q = 53
\([1,\phi(p * q) - 1]\)中任选两个数e = 17,d = 2753,使得\(e * d = 1 \quad (mod \quad \phi(p * q))\)
销毁p,q
其中,\((p * q,e) = (3233,17)\)是公钥匙,\((p * q,d)= (3233,2753)\)是私钥

实际应用中公钥和私钥都是采用ASN.1格式表达的


加密操作
假设需要加密的消息为m(必须是整数,并且 < n)
密文 = \(m^e % n\) = \(65^{17} = 2790 (mod 3233)\)


解密操作
假设需要解密的消息为c
原文 = \(c^d = m % n = 2790^2753 = 65 (mod 3233)\)

单纯型算法

uoj.179.线性规划
板子
pivot操作的复杂度显然是\(O(NM)\)的,但是最优化操作中pivot操作的调用次数可能会成为指数级。

但是我们可以发现要达到这个指数级的调用次数,边权也必须是指数级的。所以在OI中往往跑得比谁都快。所以“能在1s内跑出范围为几百的数据”。

椭球算法$ O(n^6 * m^{2})$

内点算法 \(O(n^{3.5}*m^{2})\)

改进的内点算法 \(O(n^{3.5}*m)\)


标准型

松弛型

基本性质和操作

1.解空间是一个凸区域或者无界区域
2.总在凸区域的顶点取得最值


单纯型算法步骤
确定基本可行解(在大部分情况中简单的把所有变量取0即可)
在最大化函数中,选择一个系数不为负数的变量\(x_k\)
\(x_k\)增大时,确定其他变量对\(x_k\)增长的约束(因为要求所有变量非负)
如果所有的约束都是无穷大,那么返回无界
并选择其中一个对\(x_k\)约束最紧的变量\(x_j\)
转轴\((x_k,x_j)\)
不停的做上述操作,直到最大化函数的所有变量系数都为负,此时得到单纯型的解
算法导论证明了,每次迭代都不会减少最大化函数的值,但是,存在使得最大化值不变的情况称之为退化
如果一个单纯型执行超过\(c(n + m,m)\)次的迭代,那么它就是循环的

可以在simplex中通过选择最小下标的变量,保证单纯型算法一定会终止
或者微扰变量,或者按照字典序打破一样的目标值,都可以打破循环


如何取得基本可行解,当变量全取0时,存在非负系数如何处理


转轴操作pivot
选择一个基变量\(x_B\)(换出变量)以及一个非基变量\(x_N\)(换入变量)将其互换


simplex(单纯型算法的主要操作)
即从一个基本解出发,经过一系列转轴操作,抵达最优点。通过选择特定的换入和换出变量,可以使得每一次转轴操作都能使目标函数增大,直到达到全局最优解

主定理

用来计算\(T(n) = aT(\frac{n}{b}) + f(n)\)的上界
1.若存在常数ε > 0,有\(f(n) = O(n^{log_{b}{(a)} - ε})\),则\(T(n) = \Theta (n^{log_b{a}})\)
2.若\(f(n) = O(n^{log_{b}{(a)}})\),则\(T(n) = \Theta (n^{log_b{a}} log(n))\)
3.若存在常数ε > 0,有\(f(n) = O(n^{log_{b}{(a)} + ε})\),且对常数c < 1与足够大的n,有\(af(n) \leq cf(n)\),则\(T(n) = \Theta (f(n))\)

摊还分析

1.聚合分析

给每个操作赋值一个费用(这里赋值为该操作的真实费用)
构造一个操作序列,使得它的运行复杂度最坏,记此时的复杂度为T(n)
那么T(n)显然就是该算法的最坏的真实复杂度上界
该方法的缺点是真实的计算一般很困难、复杂

2.会计分析

给每个操作赋值一个费用(这里赋值为该操作的摊还费用,可正可负)
对于任意的操作序列,使它的运行后
\(\sum{(操作次数) * (摊还代价)} \geq 0\)
此时\(\max\{\sum{[摊还代价 > 0](操作次数) * (摊还代价)} \}\)就是该算法的摊还上界
会计分析相当于是聚合分析的一个改进,避开了聚合分析时,分析真实操作时非常困难的弊端

3.势函数

和会计分析等价,区别在于会计分析是先验的,也就是你需要预先给每个操作赋值费用
首先定义势能函数,和初始势能(一般定义为0),并且要求,势能在任意操作下不能为负
然后计算,\(某个操作的摊还代价 = 真实代价 + 势能变化\)
然后计算在最坏操作序列下的摊还代价
注意,每个操作的费用是我们通过势函数 + 真实代价计算出来的
并且随着势函数的不同,计算出来的单个操作的摊还代价是不同的
最后强调一下,\(设计势函数\rightarrow 计算每个操作的摊还代价 \rightarrow 计算总的摊还代价 \rightarrow 得到复杂度上界\)

势能法分析hash表


定义
\(size[n] = 第n次操作时总的槽数\)
\(num[n] = 第n次操作时表中的对象数量\)
\(\alpha = 装载率\)


只插入,当\(\alpha > \frac{1}{2}\)时扩张(扩张复杂度为O(num[n - 1] + 1))
定义势函数 \(\psi(T) = 2 * num[T] - size[T]\),于是有\(\psi(0) = 0\),且势永不为负

若第i次操作没有触发表扩张,则

\(num[i] = num[i - 1] + 1,size[i] = size[i - 1]\)
\(摊还代价 = 真实代价 + 势能变化 = 1 + (2 * num[i] - size[i]) - (2 * num[i - 1] - size[i - 1])\)
化简得\(摊还代价 = 3\)

若第i次操作触发了表扩张

\(num[i] = size[i - 1] + 1 = num[i - 1] + 1,size[i] = size[i - 1] * 2\)
\(摊还代价 = 真实代价 + 势能变化 = num[i - 1] + 1 + (2 * num[i] - size[i]) - (2 * num[i - 1] - size[i - 1])\)
化简得\(摊还代价 = 3\)
综上所述,摊还代价为O(3 * n) = O(n)
我悟了,非常妙


插入 + 删除,删除时可能表收缩,使得\(\alpha <= \frac{1}{2}\)
定义势函数

\[\psi(T)=\left\{ \begin{array}{rcl} 2 * num[T] - size[T] & & {\alpha \geq \frac{1}{2}}\\ \frac{size[T]}{2} - num[T] & & \alpha < \frac{1}{2} \end{array} \right. \]

只分析一下第i次delete引起了表收缩,其余同上
收缩时,\(摊还代价 = 实际代价 + 势能变化 = 1 + num[i] + (\frac{size[i]}{2} - num[i]) - (\frac{size[i - 1]}{2} - num[i - 1])\)
化简得\(摊还代价= 3\)

posted @ 2021-10-11 14:26  XDU18清欢  阅读(458)  评论(0)    收藏  举报