Acm总结
学到的算法和数据结构:
STL的使用
STL是C++的标准模板库,提供了相当多的现成的库函数和数据结构,STL即可以极大地缩短代码长度,出错的概率。STL中的库函数包括sort排序函数,有find,lower_bound和upper_bound等一些查找函数用来简化代码,另外最常用的就是顺序容器和关联容器了,其实顺序容器可以相当相当程度上代替一些常用的基础数据结构如vector可以代替长度可变的数组(可以简单地实现邻接表),list可以代替链表,stack可以代替栈,deque可以代替双端队列,priority_queue可以代替我们前面提到的优先队列,而关联容器中的map可以实现任意两种类型的数据之间的索引,而set可以查找某个集合中是否存在某个元素。
贪心:贪心与其说是一种算法,更是一种思想。在构造最优解的每一步过程中,都在构造局部最优解,解这类问题关键是找到最优策略,并分析最优子结构能否构造全局最优解。
在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,直接去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解,这种求解方法就是贪心算法。
贪心算法不是从整体考虑问题,而是求局部最优解
求解过程:
(1)候选集合A:为了构造问题的解决方案,有一个候选集合A作为问题的可能解,即问题的最终解均取自于候选集合A。
(2)解集合S:随着贪心选择的进行,解集合S不断扩展,直到构成满足问题的完整解。
(3)解决函数solution:检查解集合S是否构成问题的完整解。
(4)选择函数select:即贪心策略,这是贪心法的关键,它指出哪个候选对象最有希望构成问题的解,选择函数通常和目标函数有关。
(5)可行函数feasible:检查解集合中加入一个候选对象是否可行,即解集合扩展后是否满足约束条件。
适用条件:
问题的求解由一系列步骤组成,每一个步骤都是局部的最优解,可以先求出局部最优解再求总的解
典型的贪心算法:
装载问题,背包问题,延迟调度问题。。。
简单的动态规划:包括思考问题的方式和角度,能从简单的问题中提取出动态规划的状态转移方程,关于矩阵内部运算的DP问题比较熟悉,对常见的动态规划问题和经典问题也有一定的了解。
动态规划所适用的问题是一个问题可以被分成若干个阶段,每一个阶段都需要做出决策,并且影响的下一个阶段的决策。
动态规划常常适用于重叠子问题,和最优子结构的问题。
动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。
动态规划问题满足三大重要性质
最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
子问题重叠性质:子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。
无后效性:将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。
类斐波那契数列问题,指特征方程类似于 f(n) = f(n-1) + f(n-2),的问题
01背包,一般描述为给定背包的容积,一些物品的价值和体积,求能放入背包中的物品的最大价值,一个比较通用的公式为f[i, j] = max( f[i-1, j-Wi] + Pi (j >=Wi), f[i-1, j] ),这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。一种区别这两种问法的实现方法是在初始化的时候有所不同。如果是第一种问法,要求恰好装满背包,那么在初始化时除了f[0]为0其它f[1..V]均设为-∞,这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0..V]全部设为0。
完全背包,这个问题非常类似于01背包问题,所不同的是每种物品有无限件,也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……取[V/c]件等很多种。
最大公共子序列,又称LCS,由于上课老师没有讲过,便自己学习了一下,这类问题一般描述为给定一定个字符串,求出个个串最长的公共子序列的长度,这种问题的解决思路很巧妙,用一个矩阵,行和列每个格子代表一个字符,分别表示出两个字符串,如图,引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定搜索的方向。我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] = Y[j]还是X[i] != Y[j],就可以计算出c[i][j],即问题的解。
简单dp:递推、背包、LIS(最长递增序列),LCS(最长公共子序列)
区间dp:枚举区间,把区间分成左右两部分,然后求出左右区间再合并。
搜索:
搜索分为深度优先搜索和广度优先搜索
深度搜索:
1.在当前状态下选择一种可行的情况,转入新的状态;
2.重复1直到找到答案或者确定没有解;
3.若是确定没有解(或者找另外一组解),则返回上一步,选择其他情况,直到所有情况都尝试一遍
经典问题:
迷宫问题:设定一个方向数组,表示能够走的四个方向
利用一个数组dist[x, y]记录起点到(x, y)的距离:
dfs(x, y)
for (x,y)周围4个点(tx,ty)
if a[tx, ty]不是障碍 and dist[x, y] + 1 < dist[tx, ty] then
dist[tx, ty] = dist[x, y] + 1
dfs(tx,ty)
广度搜索:
1.在当前状态下依次选择各种可行的情况,转入新的状态;
2对每个新状态重复1
3.重复1,2直到找到答案或者确定没有解
经典问题:
涂色问题:
obfs(startX, startY)
nqueue[1] = (startX, startY)
n将color[startX, startY]赋为当前颜色
nwhile queue非空 do
出队操作,得到(x, y)
for (x,y)周围4个点(tx,ty)
if a[tx, ty] = 1 and color[tx, ty]没被染色 (tx, ty)入队
将color[tx, ty]赋为当前颜色
N皇后问题
在国际象棋中,皇后可以攻击与她在同一条水平线、垂直线和对角线的棋子。现在有一张N×N的国际象棋棋盘,在上面放置N个皇后。有多少种使皇后不能互相攻击的方案?(N≤13)
(1) 深度优先搜索(DFS)
逐列(行)搜索。每测试一个位置,就检查它是否与其他皇后冲突,如果冲突,回溯[1]。
每放置一个皇后,就要记录——所在列、“/”对角线和“”对角线都不能放皇后了。
#include <iostream>
#include <cstring>
using namespace std;
bool column[20],cross1[50],cross2[50];
int pos[20];
int n, sum=0;
void dfs(int row)
{
if (row==n)
{
sum++;
return;
}
for (int i=0;i<n;i++)
if (!(column[i] || cross1[row-i+n] || cross2[row+i])) // 判断是否可以放置皇后
{
// 对皇后已经控制的列和对角线做标记
column[i]=cross1[row-i+n]=cross2[row+i]=true;
pos[row]=i;
dfs(row+1);
// 解除标记,这样才能在新位置上放置皇后。
column[i]=cross1[row-i+n]=cross2[row+i]=false;
}
}
int main()
{
cin>>n;
memset(column,0,sizeof(column));
memset(cross1,0,sizeof(cross1));
memset(cross2,0,sizeof(cross2));
dfs(0);
cout<<sum<<endl;
return 0;
}
图论:
常用概念:
子图:当图G'=(V',E')其中V‘包含于V,E’包含于E,则G'称作图G=(V,E)的子图。每个图都是本身的子图。
生成子图:指满足条件V(G') = V(G)的G的子图G。
度:一个顶点的度是指与该顶点相关联的边的条数,顶点v的度记作d(v)。
入度和出度:对于有向图来说,一个顶点的度可细分为入度和出度。一个顶点的入度是指与其关联的各边之中,以其为终点的边数;出度则是相对的概念,指以该顶点为起点的边数。
路径:从u到v的一条路径是指一个序列v0,e1,v1,e2,v2,...ek,vk,其中ei的顶点为vi及vi - 1,k称作路径的长度。如果它的起止顶点相同,该路径是“闭”的,反之,则称为“开”的。一条路径称为一简单路径(simple path),如果路径中除起始与终止顶点可以重合外,所有顶点两两不等。
行迹:如果路径P(u,v)中的边各不相同,则该路径称为u到v的一条行迹。
轨道:如果路径P(u,v)中的顶点各不相同,则该路径称为u到v的一条轨道。
kruscal算法
kruskal算法,即求加权连通图的最小生成树的算法。kruskal算法总共选择n- 1条边,(共n个点)所使用的贪婪准则是:从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中。注意到所选取的边若产生环路则不可能形成一棵生成树。kruskal算法分e步,其中e是网络中边的数目。按耗费递增的顺序来考虑这e条边,每次考虑一条边。当考虑某条边时,若将其加入到已选边的集合中会出现环路,则将其抛弃,否则,将它选入。
将边按权值从小到大排序后逐个判断,如果当前的边加入以后不会产生环,那么就把当前边作为生成树的一条边。
最终得到的结果就是最小生成树。
,PRIME算法:
prim算法:
1).输入:一个加权连通图,其中顶点集合为V,边集合为E;
2).初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;
3).重复下列操作,直到Vnew = V:
a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
b.将v加入集合Vnew中,将<u, v>边加入集合Enew中;
4).输出:使用集合Vnew和Enew来描述所得到的最小生成树。
最短路的DIJKSTRA,floyed算法。
图的邻接表以及邻接矩阵表示
编程水平的提升:
感觉根据算法直接写代码的能力强了很多,对稍微复杂问题的分析能力,解决问题能力得到了全面的提升。还有就是编程中出现的BUG越来越少了。熟悉很多有用的函数,对字符串的处理能力,简化代码的能力,调试代码的能力都提升了一个档次。
知识的拓展:
感觉自己的知识面广了很多,ACM涉及都知识可以广至图论数论博弈论组合数学数据结构算法等等。很多东西是无法归纳到上面某一类中的,区分有用知识和无用知识,还有自学的能力,还有根据所学的知识解决和分析实际问题的能力提高了。
总结:
一, 凡事贵在坚持。ACM对一个人的逻辑思维能力要求很强。当在做题过程中碰到难题时要耐得住心,沉住气,不断地思考,有不做出不罢休的精神。
二, 对自己要有信心,要相信自己。当一个题目做出来提交出差后,不要急,回过头来把题目再认真读一遍,注意输入输出的格式,对照代码进行修改,改好了再提交,不能因为一次有错就盲目的提交。在修改代码的过程中要多想想为什么,这样才能提高自己