算法刷题

ACM笔记初探ACMGCD / LCM 求法快速幂循环节贪心算法常见模型Havel Hakimi 定理快速排序递推问题简单递推条件递推Catalan数动态规划DP经典1:数塔问题DP经典2:最长递增子序列问题DP经典3:最长公共字符串子序列问题背包问题01背包完全背包多重背包二维背包搜索BFS二分查找三分查找DFS优化筛选法求N以内所有素数筛选法+统计 求N以内素数的个数并查集与最小生成树Kruskal算法Prim算法计算几何拓扑排序博弈最短路 - Dijkstra二分图-匈牙利算法复习心得

ACM笔记

初探ACM

GCD / LCM 求法

求LCD的方法: LCM = a * b / gcd(a, b)

快速幂

幂次拆分,提升效率。

非递归形式实现:

循环节

抽屉原理:总共n个抽屉,往里面随便放n+1个物体,则必然存在一个抽屉,它包含两个物体

一般看到 mod 就要想到用循环节做。

贪心算法

常见模型

问题1:FatMouse Trade 问题,解法:求性价比降序排列,从性价比最高的开始买,钱不够时全部按比例折算购买。(hdu FatMouse's Trade)

问题2:最长事件序列问题,解法:最长的事件序列必然包含最早结束的事件序列。(hdu 今年暑假不Ac)

问题3:田忌赛马问题,策略:

来源: https://blog.csdn.net/liangbopirates/article/details/10035865

Havel Hakimi 定理

问题3:求图的连通性,给出连通性关系矩阵。(poj 1659)

Havel-Hakimi算法的依据:给出G,G是一个递减序列:

并且G是连通图,那么当且仅当递减序列G alt中的数是非负整数且G alt 也连通。G alt 序列是递减的,并且从第一个数 S1 开始的连续 S1 个数都减少 1:

根据 Havel 定理,如果 G 连通,那么不断向前递推,必然能得到一个全部是 0 的序列。否则 G 不能连通。

解法:

快速排序

后来我发现用选择排序效率低导致超时,因此使用快速排序,成功Ac,快速排序如下:

直接用#include <algorithm>sort() 最好。

递推问题

简单递推

求Fibonacci数列的第n项:应使用递推 O (n) 而不是递归 O (2^n)

求n条直线能把平面分成多少部分:F(n) = F(n-1) + n

求n条折现能把平面分成多少部分:F(n) = F(n-1) + 4(n-1) + 1 (新增一条折线增加四个点,增加点数+1个段数)(HDUOJ 2045)

条件递推

Children's Queue 问题:女生必须两个以上连着排在一起,求n个人有几种可能的排法?

Lele的RPG难题:有RPG三种颜色,填在一行中,每一格颜色不同且首位颜色不同,求n个格子有几种可能的排法?

思路:以 前面合法 + 前面不合法 为分析基础,得出递推公式:

思路解释:第n种情况由两种类型组成:

  1. n-1情况正常,结尾加上唯一可能的字母;
  2. n-1情况结尾和首字母相同时(即n-1不合法但n-2情况合法),最后加上两种可能的字母。

大兔生小兔问题:F(n) = F(n-1) + F(n-2)

骨牌拼接问题:有2x1,2x2两种骨牌,铺2xn的房间,如果最后一块放2x1,只有一种方法,而当最后一块放2x2则有两种方法(横着的两块2x1,或者一块2x2),所以F(n) = F(n-1) + 2 x F(n-2)

 

Catalan数

前几项Catalan数:1 2 5 14 42 132 429 ... ...

前几项Catalan数:1 2 5 14 42 132 429 ... ...

Catalan数 模板题1: HDUOJ 1134 一圈上的点连线,线不相交(划分子问题)

注意:上面的代码不能 Ac,因为后面数字太大,即便是 long long都放不下。建议用 Java bigInteger类实现。

Catalan数 模板题2: HDUOJ 2067 只允许下三角行走方格的最短路线数(也可用Dp)

其他题目

加括号的方式

N行N列网格路线(只允许下三角行走)

N个节点的二叉树的形态种类

HDOJ 1133 Buy the Ticket

相关练习 1023 1130 1131 1134 1133 1267 2067**

动态规划

DP问题适用的条件:

1.重叠子问题

2.无后效性

3.最佳子结构

DP经典1:数塔问题

问题描述:有一个数塔,每个节点有一个权值,怎么走路线长度最大?

分析:贪心算法会导致短视,应当采用DP,自顶向下分析,自底向上计算,从已知最优解推未知最优解。

HDUOJ 免费披萨 作为例题参考:

问题描述:有一个一维坐标0~10,Gameboy在坐标5,每单位时间只能左右移动一格或者不动。输入的数据是披萨出现的时间点和位置,问Gameboy最多能抢到多少披萨?

分析:输入的数据可以转换成 时间 + 位置 的数塔,而GameBoy的移动策略则正好成为状态转移方程的一部分(数塔层之间的关联)。因此这个问题本质上就是DP模板

DP经典2:最长递增子序列问题

问题描述:给定一个数组,求数组最长递增子序列的所有序列元素的和。

分析:求n个元素的最长子序列之和,可以化为求n-1个中最长子序列之和加上某个元素,并可以继续向小规模问题转化。求1个元素的最长子序列之和是最小的子问题。方法仍然是自顶向下分析,自底向上递推。

问题描述:给出老鼠速度和质量,要求找出最长序列,要求老鼠速度递增但质量递减,并给出老鼠ID。

分析:本质是最长递增子序列,但要求回溯路径。

DP经典3:最长公共字符串子序列问题

问题描述:给定两个字符串,求最长公共字符串子序列。

分析:状态转移方程如下:

背包问题

有一个容量为V的背包和若干个物品,在一定限制条件下,最多能放多少物品?

01背包

每种物品有且仅有一个,可以选择买或者不买。

状态转移方程:

式中,F ( i , v ) 表示前 i 件物品装入容量为 v 的背包的价值。c( i ) 表示费用,w ( i ) 表示价值。

01样板代码:

注意要从 j 要从0开始,要考虑 0 背包大小。

01背包优化代码,不容易理解但使用推荐这个:

 

完全背包

一个物品可以取无数个,此外和01背包一样。

优化代码,建议使用这个:

 

多重背包

一个物品可以买特定的数量。一种方法是逐个将堆叠的物品转化为独立的数量为1的物品,从而变为01背包;优化的方法是将堆叠物品用2的倍数分割。

以下样板代码,将情况分为多重背包 + 01背包。

 

二维背包

物品的限制有两种,如背包的承重,背包的容量两类限制。

状态转移方程变为三维:

搜索

BFS

BFS是基于队列的。下面的代码是二叉树的层次遍历:

二分查找

前提是单调性。

 

 

三分查找

前提是求导一次后符合单调性。

 

DFS

深度优先搜索是基于栈的。

优化

筛选法求N以内所有素数

  1. 判断素数的优化:只需判断到 sqrt( N ) 即可。
  2. 筛选法:设置一个数组,值为1时认定为素数,值为0时认定为非素数。先全部置1,然后遍历判断,当前下标单元值为0时无需处理,如果是1,则将其倍数全部置为0,因为不论这个数是不是质数,他的倍数一定不是质数。
  3. 筛选法的优化版本:

筛选法+统计 求N以内素数的个数

  1. 首先通过筛选法进行预处理。
  2. 在筛选法进行过程中,统计 直到当前 i 位置,总共出现的质数的个数。
  3. 处理完毕后,输入的数据直接从数组中寻找并输出即可。

并查集与最小生成树

From12345678910
Master1133367777

每个元素都用其所属集合中最小的元素来标记。

Kruskal算法

利用并查集实现。

先把所有边从小到大排序,依次选择从小到大的边,如果边的两个顶点不在一个集合中,则合并两个集合并作记录,否则放弃选择。按上述步骤得到的即为最小生成树。

增加一个方法,判断是否属于同一个集合:

因为有count个点,所以最少需要c-1条路。每个循环中,先在邻接矩阵中找到不在同一集合中的最小距离的两个点,进行合并。

 

Prim算法

 

计算几何

给定两条有向线段 A-->B,B-->C:

问题1:求A-->B在B-->C的顺时针方向还是逆时针方向:

(A-->B) x (B-->C) = (A-->B)(B-->C) sin(theta) > 0 ==> sin(theta) > 0 ==>

Assert theta < rad90, then theta > 0 ==> (A-->B) clockwise (B-->C), vice versa, qed.

 

问题2:A-->B-->C,判断B点是左转还是右转:

(B-->C) x (B-->A) > 0 ==> (B-->C)(B-->A) sin(theta) > 0 ==> sin(theta) > 0 ==>

Assert theta < rad90, then theta > 0 ==> (B-->C) clockwise (B-->A) ==> B - LeftTurn qed.

 

问题3:判断A--B && B--C是否相交:

若不存在某个点位于某条线段的相交情况,则有:

if (A-->B) x (A-->C) * (A-->C) x (A-->D) < 0 && (C-->D) x (C-->A) * (C-->D) x (C-->B) < 0 then A--B intersect B--C

若存在某个点位于某条线段上的相交情况,则在上面情况的基础上,增加:

if (A-->B) x (A-->C)

问题4:多边形面积计算:

原理是三角形划分,用到了叉乘的带符号面积,且以原点为中心。

问题5:凸包问题算法

  1. Graham Scan Algorithm --- O(nlogn)

    1. 选取一个y坐标最小的点(数学坐标系),将其与其他点连线,根据连线与极坐标正方向的夹角大小排序(两个向量叉乘,根据正负判断是顺时针方向还是逆时针方向)。
    2. 将前两个点加入栈S,判断栈顶两个点连成的向量与栈顶点和第三个点连成的向量是否构成逆时针旋转,如果是,将第三个点压入栈,否则开始退栈。
    3. 直到所有点遍历完毕,栈中的点元素就是凸包上的点。
  2. Jarvis March Algorithm --- O(n^2)

    选y值最小的点,开始从0rad顺时针旋转,每次第一个相遇的点作为新的点,加入容器后继续上述操作,直到与第一个点相遇,即得到凸包。

拓扑排序

找入度为0的点,找到后删除,继续查找,若不存在入度为0的点,并且已经找完所有点,则原有向无环图可以进行拓扑排序,反之,原图存在环,不能进行拓扑排序。

博弈

必胜点N点:上一个人将取胜的点。

必败点P点:下一个人将失败的点。

终结点必定是必败点;必胜点至少有一种方法可以进入必败点;必败点只能进入必胜点。

组合游戏解法:

  1. 所有终结位置标为P,所有一步操作能进入P的点的标为N点。
  2. 如果从某个点开始所有的一步操作只能进入N点,则将该点标为P点。

[例] 假设某取子游戏一次只能取1、3、4张牌,先取完的人获胜,则 N P 标注方式如下:

01234567891011
PNPNNNNPNNPN

定理1:对于nim游戏的某个位置(x1 x2 x3)当且仅当各部分的 nim-sum = 0时,当前位置是必败点。

状态转移图

Sprague-Grundy函数

最短路 - Dijkstra

 

100
50
10
A
B
C

关键:更新最短路径的方式——两边之和小于第三边:

distance[j] = Math.min(distance[minpos] + map[minpos][j],distance[j])

例如上图,A --> C 的最短路径从100更新到50+10=60

二分图-匈牙利算法

复习心得