@Voleking
2017-08-28T09:46:12.000000Z
字数 7676
阅读 1229
说实话,真心觉得入门书和白书结构安排和讲解都有点奇怪,于是把高中的这本《Free Pascal 语言与基础算法》(真心是非常赞的一本基础算法书)拿出来重新复习一遍基础算法。寒假都去弄博客了,结果到开学第四周才弄完,简直💊。本来想边复习边总结然后分享给大家的,但是发现按照我的理解最多贴几个自己看的懂的版子就好,但是要弄到给别人看的程度内容则要几何倍的膨胀,最后就成了这个高不成低不就的四不像。因为寒假还没看 C++ ,所以绝大多数内容都是由 C 完成的,导致代码显得比较臃肿。V0.0 大概就这样吧,
如果以后用空再完善。另外白书和入门书没剩多少新内容了,只剩题了Orz。立个 flag 这学期一定要刷完!太年轻( ・ˍ・)
STL 大法好,从此排序一行搞!
不过各种排序代码也简单,很多方法背后的思想挺经典的,就大致打了打。没准有水题像「车厢调度」用到框架~(第一次在 CF 上做到一到类似的题还想了一下)
for (int i = 0; i < n; i++)for (int j = 0; j < n - i - 1; j++)if (a[j] > a[j + 1]) {int temp = a[j];a[j] = a[j + 1];a[j + 1] = temp;}
for (int i = 1; i < n; i++) {int tmp = a[i], j;for (j = i - 1; j >= 0 && a[j] > tmp; j--)a[j + 1] = a[j];a[++j] = tmp;}
for (int i = 0; i < n; i++)for (int j = i + 1; j < n; j++)if (a[i] > a[j]) {int temp = a[i];a[i] = a[j];a[j] = temp;}
int mersgesort(int a[], int b[], int c[], int an, int bn) {int i = 0, j = 0, k = 0;while (i < an && j < bn) {if (a[i] < b[j]) {c[k++] = a[i++];}else {c[k++] = b[j++];}}while (i < an)c[k++] = a[i++];while (j < bn)c[k++] = b[j++];return k;}
void quicksort(int a[], int l, int r) {int i = l, j = r, mid = a[(l + r) / 2];while (i <= j) {while (a[i] < mid) i++;while (a[j] > mid) j--;if (i <= j) {int tmp = a[i];a[i] = a[j];a[j] = tmp;i++;j--;}}if (i < r) quicksort(a, i, r);if (l < j) quicksort(a, l, j);return ;}
棋盘格数
n*m 方格棋盘:
包含正方形个数
包含长方形个数(乘法原理,含正方形)
主要是理解思想,几道简单的练习题:
int dfs(int step, ...parameter list) {if success {print;return 0;}for (;;)if satisfied {save condition;dfs(step++, ...parameter list);//recover precondintion;}}
void bfs() {初始化,初始状态存入队列;队列首指针 head = 0,尾指针 tail = 1;while (head < tail) {if 当前节点为目标节点输出并退出;for (;;) {if satisfied & unreached {存入新节点;tail++;}}head++;}}
相比与 DFS,BFS 常用于求最优解。
思想:局部最优推导整体最优。常需要排序等预处理。
大胆猜测,细心验证
在多阶段决策问题中,各阶段采取的决策,一般来说与阶段有关的,决策依赖于当前状态,又随即引起状态的转移,一个决策序列就是在变化的状态中产生出来的,故有“动态”的含义,我们称解决多阶段决策最优化过程为动态规划程序设计方法。
基本概念于模型构成:
需满足条件:
for (int i = 0; i < n; ++i)for (int v = V; v >= w[i]; v--)f[v] = max(f[v], f[v- w[i]] + c[i]);
for (int i = 0; i < n; ++i)for (int v = w[i]; v <= V; ++v)f[v] = max(f[v], f[v- w[i]] + c[i]);
注意循环顺序的不同背后思路。
转化为 01 背包问题求解:
多重背包
for (int i = 0; i < N; ++i) {int k;for (k = 1 << 0; k <= n[i] && w[i] * k <= V; n[i] -= k, k <<= 1) {for (int v = V; v >= w[i] * k; --v)f[v] = max(f[v], f[v- w[i] * k] + c[i] * k);}k = n[i];if ( k > 0 && w[i] * k <= V) {for (int v = V; v >= w[i] * k; --v)f[v] = max(f[v], f[v- w[i] * k] + c[i] * k);}}
弄清楚上面三种背包后分情况就好
二维费用可由最多取 m 件等方式隐蔽给出。
for (int k = 0; k < K; ++k)for (v = V; v >= 0; --v)for (int i = 0; i <= n[k]; ++i)f[v] = max(f[v], f[v - w[i]] + c[i]);
显然可以对每组中物品应用完全背包中“一个简单有效的优化”
由 NOIP2006 金明的预算方案引申,对每个附件先做一个 01 背包,再与组件得到一个 个物品组。
更一般问题,依赖关系由「森林」形式给出,涉及到树形 DP 以及泛化物品,这里不表。
更多内容详见「背包九讲」
head:队头指针;tail:尾指针。
优化——循环队列:
tail = (tail + 1) / n;
if (head == tail) 队列已满;
else Q[tail] = X;
void put(int x) {a[++len] = x;int p = len;while (p != 1 && a[p] < a[p / 2]) {int tmp = a[p];a[p] = a[p / 2];a[p / 2] = tmp;p /= 2;}return ;}int get() {int num = a[1];a[1] = a[len--];int fa = 1, son = 0;while (fa * 2 <= len) {if (fa * 2 + 1 > len || a[fa * 2] < a[fa * 2 + 1])son = fa * 2;elseson = fa * 2 + 1;if (a[fa] > a[son]) {int tmp = a[fa];a[fa] = a[son];a[son] = tmp;fa = son;}elsebreak;}return num;}
void sift(int i) {Data tmp = a[i];while (i * 2 <= len) {if (i * 2 + 1 > len || a[i * 2].fish > a[i * 2 + 1].fish)i = i * 2;elsei = i * 2 + 1;if (tmp.fish < a[i].fish)a[i / 2] = a[i];else {i /= 2;break;}}a[i] = tmp;}// 建堆for (int i = 1; i <= n / 2; ++i)hp.sift(i);
简单定义与概念:
图的存储与遍历:
init: dis[u][v] = w[u][v]() or INT_MAX / 3 //防止判断时超出整数范围for (int k = 1; k <= n; ++k)for (int i = 1; i <= n; ++i)for (int j = 1; i <= n; ++j)dis[i][j] = min(dis[i][k] + dis[k][j], dis[i][j]);
如果规定每条边只走一次,可以求出负权回路的最短路径。修改循环顺序。
算法描述(蓝白点思想):
设起点为 s,dis[v] 表示从 s 到 v 的最短路径,pre[v] 为 v 的前驱节点,用来输出路径。
memset(vis, 0, sizeof(vis));for (int i = 1; i <= n; ++i) {dis[i] = (i != s)? w[s][i] : 0;}for (int i = 1; i <= n; ++i) {double mmin = DBL_MAX / 3;int k = 0;for (int j = 1; j <= n; ++j)if (!vis[j] && dis[j] < mmin) {mmin = dis[j];k = j;}if (!k) break;vis[k] = 1;for (int j = 1; j <= n; ++j) {dis[j] = min(dis[k] + w[k][j],dis[j]);}}
算法实现:
设起点为 s,dis[v] 表示从 s 到 v 的最短路径,pre[v] 为 v 的前驱节点,用来输出路径。
init: dis[v] = ∞ (v ≠ s); dis[s] = 0; pre[s] = 0;for(int i = 1; i < N; ++i)for (int j = 1; j <= E; ++j)if (dis[u] + w[j] < dis[v]) {dis[v] = dis[u] + w[j];pre[v] = u;id}
可以处理存在负边权的情况,但无法处理存在负权回路的情况。若算法完成后,还出现某条边使得:,则存在负权回路。如果一开始将 d 初始化为0,那么可以找出所有负圈。
对 Bellman-Ford 算法的一种队列实现,减小冗余计算。
主要思想:
初始时将起点加入队列,每次从队列中取出一个元素,并对与它相邻的点进行修改,如果某个相邻的点修改成功,则将其入队(已经v入队的无需入队)。直到队列为空是算法结束。注意一个点可以多次入队,使用循环队列可以使队列长度只需开到 。另外需要用邻接表储存。
for (int i = 1; i <= n; ++i) {dis[i] = (i != s)? DBL_MAX / 3 : 0;}memset(vis, 0, sizeof(vis));vis[s] = true;que.push(s);while (!que.empty()) {int u = que.front();que.pop();vis[u] = false;for (int i = 1; i <= adj[u][0]; ++i) {int v = adj[u][i];if (dis[v] > dis[u] + w[u][v]) {dis[v] = dis[u] + w[u][v];if (!vis[v]) {que.push(v);vis[v] = true;}}}}
for (int k = 1; k <= n; ++k) {for (int i = 1; i < k; ++i)for (int j = i + 1; j < k; ++j)ans = min(dis[i][j] + w[i][k] + w[k][j])for (int i = 1; i <= n; ++i)for (int j = 1; j <= n; ++j)dis[i][j] = min(dis[i][k] + dis[k][j], dis[i][j]);}
求有向图的强连通分量
Kosaraju 算法可以求有向图中强连通分量,描述如下:
有趣的一道题:刻录光盘
int find(int x) {return (f[x] == x)? x : f[x] = find(f[x]);};int merge(int a, int b) {int fa = find(a);int fb = find(b);if (fa != fb)f[fa] = fb;// f[find(a)] = find(b);}
算法描述:
以 1 为起点生成最小生成树,min[v] 表示蓝点 v 与白点相连的最小边权。
for (int i = 1; i <= n; ++i) {int k = 0, mmin = INT_MAX;for (int j = 1; j <= n; ++j)if (!visit[j] && dis[j] < mmin) {mmin = dis[j];k = j;}visit[k] = true;total += di0s[k];for (int j = 1; j <= n; ++j) if (!visit[j])dis[j] = min(g[k][j], dis[j]);}
算法描述:
按边排序,并查集维护
用有向无环图表示一个工程中每个子工程的先后关系称为“AOV 网”,把一条有向边起点的活动称为终点活动的「前驱活动」,同理终点的活动称为起点活动的「后继活动」。只有一个活动全部的前驱全部完成之后,这个活动才能进行。
算法描述:
1. 选择一个入度为 0 的顶点并输出;
2. 然后从 AOV 网中删除此顶点及以此结点为起点的所有关联边(更新终点的入度);
3. 重复 1,2 两步直到不存在入度为 0 的顶点为止;
4. 若输出的顶点小于网络中的顶点数,则有回路,否则输出结点序列为拓扑序列。
一个工程的每项活动先后关系及所需时间通过带边权的有向无环图表示,则该网络称为“AOE 网”。为了估算某个工程的时间,需要找出影响工程完成时间的主要活动。其中源点和汇点分别表示工程的开始和结束。
算法描述:
用拓扑排序顺序依次计算所有时间最早发生时间(其中事件 j 是事件 k 的直接前驱事件):