ACM课程总结

绪论

    一学期的ACM课程终于结束了,还记得当初ACM新生赛自己轻轻松松拿了个一等奖,寒假的几次比赛也让我觉得难度不是很大,自然而然觉得所谓的ACM也不过如此,于是信心满满地选了这门课。直到上了几次课把基础的地方学完开始进阶的时候,瞬间感受到什么叫一脸懵逼……于是一学期的ACM课几乎都是懵着过来的。
    一学期的课程让我学到了很多知识,认识了很多很厉害的大神,同时也意识到了自己很多的不足。在我看来,ACM就像是程序设计界的高数,数学素养真的很重要,我因为本身数学不是太好,所以很多地方理解起来很吃力,于是很多题目一遍一遍的WA,有时候真有种欲哭无泪的感觉。期间有好几次觉得太难而想要放弃,但最终还是在朋友的鼓励下坚持下来,虽然现在也不能保证能A出来每一道练习题,但是多多少少能有一些自己的思路。感觉自己不太适合这条路,自己的思维方式总是很奇怪,书中的许多算法理解起来也比较吃力,应用就更困难了。所以这次ACM课程结束,我的ACM征途可能也结束了,但以后我还是会关注ACM网站,空下来的时候也会去刷两道题。总的来说,很感谢能有这么一次机会让我看到自己的缺点,认识到了自己的不足之处,提升了自己的能力。
    废话少说,进入正题。

ACM介绍

    ACM/ICP:国际大学生程序设计竞赛(International Collegiate Programming Contest by Association for Computing Machinery)是由国际计算机学会(ACM)主办的,一项旨在展示大学生创新能力、团队精神和在压力下编写程序、分析和解决问题能力的年度竞赛。经过近30多年的发展,ACM国际大学生程序设计竞赛已经发展成为最具影响力的大学生计算机竞赛。
宗旨:1、建立程序设计训练的常态机制和竞争机制。
     2、培养解决问题的综合能力、创造能力和团队合作精神。
     3、挑选和发掘世界上最优秀的程序设计人才。
本学期的ACM课程总共学习了STL、递归递推、动态规划、背包问题、二分三分、贪心算法、搜索、数据结构、图论,等方面的内容,首先说一下STL。

STL(标准模板库)

    STL是很方便的模板库让我们可以简单操作各种c++自带类和函数。比如:排序算法、字符串操作、队列、栈、动态数组、map容器。
    这里边我最常用的是sort函数,一个函数简单、快速解决各种排序难题。STL并不复杂,或者说,没什么好讲的。

递归递推算法

    递归函数:直接或间接调用自己的函数成为递归函数,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
    递归算法解题的步骤:
        1.  分析问题、寻找递归。
        2.  设置边界、控制递归。
        3.  设计函数、确定参数。
例:求N!
    Fact(int n)
    { if(n==0\n==1)
        return 1;//条件满足,退出循环
      else
        return (n*fact(n-1));//条件不满足,进入下一次循环。
    }
    递推算法:把一个复杂的问题求解,分解成连续若干步简单运算。首要问题是得到相邻数据项间的关系。

动态规划

    动态规划:解决多阶段决策问题的一种方法。多阶段决策问题即:如果一类问题的求解过程可以分为若干个相互联系的阶段,在每一阶段都需作出决策,并应影响到下一阶段的决策。多阶段决策问题就是要在可以选择的那些策略中间选取一个最优策略,使之在预定标准下达到最好的效果。
    动态规划的基本模型:
        1.  问题具有多阶段决策的特征。
        2.  每一阶段都有相应的状态与之对应,描述状态的量称为状态变量。
        3.  每一阶段都面临一个决策,选择不同的决策会导致下一阶段的不同状态。
        4.  每一阶段的最优解问题可以递归地归结为下一阶段各个可能状态的最优解问题,各子问题与原问题具有完全相同的结构。

    动态规划的一般解题步骤:
        1.  判断问题是否具有最优子结构的性质,若不具备则不能用动态规划。
        2.  把问题分成若干个子问题。
        3.  建立状态转移方程
        4.  找出边界条件
        5.  将已知边界值代入方程
        6.  递归求解。
    例题:
    给定一个具有N层的数字三角形如下图,从顶至底有多条路径,每一步可沿左斜线向下或沿右斜线向下,路径所经过的数字之和为路径得分,请求出最大路径得分。

这里写图片描述

int Max(int i,int j) //从当前位置开始的可得的最优值
{
  int s1,s2;                  //记录从左右斜线向下走的可达的最优值
  if(a[i][j]!=-1 return a[i][j];
  if (i>n) ||(j>0) return 0 ;//当前位置不存在,最优值为-1
  s1=Max(i+1,j)+triangle[i,j]; //沿左斜线向下走
  s2=Max(i+1,j+1)+triangle[i,j]; //沿右斜线向下走
  if s1>s2 return s1 
  else return s2; 
}

背包问题

    背包问题(Knapsack problem):是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。背包问题有01背包(动态规划)、完全背包(贪心算法)、多重背包三种。
    01背包:01背包是背包问题中最简单的问题。01背包的约束条件是给定几种物品,每种物品有且只有一个,并且有权值和体积两个属性。在01背包问题中,因为每种物品只有一个,对于每个物品只需要考虑选与不选两种情况。如果不选择将其放入背包中,则不需要处理。如果选择将其放入背包中,由于不清楚之前放入的物品占据了多大的空间,需要枚举将这个物品放入背包后可能占据背包空间的所有情况。
    例:一个背包总容量为V,现在有N个物品,第i个 物品体积为weight[i],价值为value[i],现在往背包里面装东西,怎么装能使背包的内物品价值最大?
int N,M;
cin>>N;//物品个数
cin>>M;//背包容量
for (int i=1;i<=N; i++)
{
    cin>>weight[i]>>value[i];
}
for (int i=1; i<=N; i++)
    for (int j=1; j<=M; j++)
    {
        if (weight[i]<=j)
        {
            f[i][j]=max(f[i-1][j],f[i-1][j-weight[i]]+value[i]);
        }
        else
            f[i][j]=f[i-1][j];
    }
cout<<f[N][M]<<endl;//输出最优
    完全背包:是一种经典的数学问题,是研究一个固定容量的背包内能装多大价值的东西的问题。
    题目:一个背包总容量为V,现在有N个物品,第i个 物品体积为weight[i],价值为value[i],每个物品都有无限多件,现在往背包里面装东西,怎么装能使背包的内物品价值最大?
    伪代码:
    for i=1……N
    for j=1……M
    f[j]=max(f[j],f[j-weight[i]+value[i])
        int N,M;  
cin>>N;//物品个数  
cin>>M;//背包容量  
for (int i=1;i<=N; i++)  
{  
   cin>>weight[i]>>value[i];  
}  
for (int i=1; i<=N; i++)  
    for (int j=1; j<=M; j++)  
    {  
        if (weight[i]<=j)  
       {  
           f[j]=max(f[j],f[j-weight[i]]+value[i]);  
       }             
}    
cout<<f[M]<<endl;//输出最优解
    多重背包:有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。(代码略)

    比较三个题目,会发现不同点在于每种背包的数量,01背包是每种只有一件,完全背包是每种无限件,而多重背包是每种有限件。

二分三分

    二分查找:在一个单调有序的集合中查找元素,每次将集合分为左右两部分,判断解在哪个部分中并调整集合上下界,重复直到找到目标元素。优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,二分查找方法适用于不经常变动而查找频繁的有序列表。
    二分查找的基本思想是:将n个元素分成大致相等的两部分,取a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止;如果x<a[n/2],则只要在数组a的左半部分继续搜索x,如果x>a[n/2],则只要在数组a的右半部搜索x。
    伪代码:
        BinarySearch(max,min,des)
        mid-<(max+min)/2
        while(min<=max)
        mid=(min+max)/2
        if mid=des then
        return mid
        elseif mid >des then
        max=mid-1
        else
        min=mid+1
        return max
    三分查找:当需要求某凸性或凹形函数的极值,通过函数本身表达式并不容易求解时,就可以用三分法不断逼近求解。三分查找主要针对的是凸性函数给定函数值,求自变量值的情况,难度较大,是在二分的基础上,对某一区间再次二分的一种算法。已知左右端点L、R,要求找到白点的位置。
    总结:对于求解一些实际问题,当公式难以推导出来时,二分、三分法可以较为精确地求出一些临界值,且效率也是令人满意的。灵活地应用这些方法对解题会很有帮助。

贪心算法

    贪心算法:在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,直接去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解,这种求解方法就是贪心算法。
    从贪心算法的定义可以看出,贪心算法不是从整体上考虑问题,它所做出的选择只是在某种意义上的局部最优解,而由问题自身的特性决定了该题运用贪心算法可以得到最优解。如果一个问题可以同时用几种方法解决,贪心算法应该是最好的选择之一。
    能够使用贪心算法的问题必须满足下面两个性质:
        1.整体的最优解,可以通过局部最优解得出。
        2.一个整体可以分割成多个部分,每一部分都可以生成一个最优解。

    贪心算法的求解过程:
        1.  使用贪心算法求解问题应该考虑如下几个方面:
        2.  候选集合A:为了构造问题的解决方案,有一个候选集合A作为问题的可能解,即问题的最终解均取自于候选集合A。
        3.  解集合S:随着贪心选择的进行,解集合S不断扩展,直到构成满足问题的完整解。
        4.  解决函数solution:检查解集合S是否构成问题的完整解。
        5.  选择函数select:即贪心策略,这是贪心法的关键,它指出哪个候选对象最有希望构成问题的解,选择函数通常和目标函数有关。
        6.  可行函数feasible:检查解集合中加入一个候选对象是否可行,即解集合扩展后是否满足约束条件。

    贪心算法的一般流程:
//A是问题的输入集合即候选集合
Greedy(A)
{
  S={ };           //初始解集合为空集
  while (not solution(S))  //集合S没有构成问题的一个解
  {
    x = select(A);     //在候选集合A中做贪心选择
    if feasible(S, x)    //判断集合S中加入x后的解是否可行
      S = S+{x};
      A = A-{x};
  }
  return S;
}

搜索

    搜索算法:搜索算法是利用计算机的高性能来有目的地穷举一个问题的部分或所有的可能情况,从而求出问题的解的一种方法。相比于单纯的枚举算法有了一定的方向性和目标性。算法是在解的空间里,从一个状态转移(按照要求拓展)到其他状态,这样进行下去,将解的空间中的状态遍历,找到答案(目标的状态)。搜索算法分为广度优先搜索(BFS)、深度优先搜索(DFS)。
    广度优先搜索(BFS):从初始状态S 开始,利用规则,生成所有可能的状态。构成的下一层节点,检查是否出现目标状态G,若未出现,就对该层所有状态节点,分别顺序利用规则。生成再下一层的所有状态节点,对这一层的所有状态节点检查是否出现G,若未出现,继续按上面思想生成再下一层的所有状态节点,这样一层一层往下展开。直到出现目标状态为止。——在路径的寻找问题上用得比较多。
    框架:
    While Not Queue.Empty ()
    Begin
    可加结束条件
    Tmp = Queue.Top ()
    从Tmp循环拓展下一个状态Next
    If 状态Next合法 Then
    Begin
    生成新状态Next
    Next.Step = Tmp.Step + 1 
    Queue.Pushback (Next)
    End
    Queue.Pop ()
    End

    深度优先搜索(DFS):从初始状态,利用规则生成搜索树下一层任一个结点,检查是否出现目标状态,若未出现,以此状态利用规则生成再下一层任一个结点,再检查,重复过程一直到叶节点(即不能再生成新状态节点),当它仍不是目标状态时,回溯到上一层结果,取另一可能扩展搜索的分支。采用相同办法一直进行下去,直到找到目标状态为止。
    框架:
    递归实现:
    Function Dfs (Int Step, 当前状态)
    Begin
    可加结束条件
    从当前状态循环拓展下一个状态Next
    If 状态Next合法 Then
    Dfs (Step + 1, Next ))
    End

    非递归实现:
    While Not Stack.Empty ()
    Begin
    Tmp = Stack.top()
    从Tmp拓展下一个未拓展的状态Next
    If 没有未拓展状态(到达叶节点) Then
    Stack.pop() 
    Else If 状态Next合法 Then
    Stack.push(Next)
    End

数据结构

     栈和队列:在STL那里介绍了栈和队列的用途以及使用方法,在这里重点讲的是栈和对列的实现方法。
    树以及二叉树:一棵树是由n(n>0)个元素组成的有限集合,其中:
    (1)每个元素称为结点(node);
    (2)有一个特定的结点,称为根结点或树根(root);
    (3)除根结点外,其余结点能分成m(m>=0)个互不相交的有限集合T0,T1,T2,……Tm-1。其中的每个子集又都是一棵树,这些集合称为这棵树的子树。
    树的基本概念:
    1.  一个结点的子树个数,称为这个结点的度;度为0的结点称为叶结点;度不为0的结点称为分支结点;根以外的分支结点又称为内部结点(结点2、4、7);树中各结点的度的最大值称为这棵树的度(这棵树的度为3)。
    2.  在用图形表示的树型结构中,对两个用线段(称为树枝)连接的相关联的结点,称上端结点为下端结点的父结点,称下端结点为上端结点的子结点。称同一个父结点的多个子结点为兄弟结点。称从根结点到某个子结点所经过的所有结点为这个子结点的祖先。称以某个结点为根的子树中的任一结点都是该结点的子孙。 
    3.  C.定义一棵树的根结点的层次(level)为1,其它结点的层次等于它的父结点层次加1。一棵树中所有的结点的层次的最大值称为树的深度(depth)。
    4.   D.对于树中任意两个不同的结点,如果从一个结点出发,自上而下沿着树中连着结点的线段能到达另一结点,称它们之间存在着一条路径。可用路径所经过的结点序列表示路径,路径的长度等于路径上的结点个数减1。
    5.  E.森林(forest)是m(m>=0)棵互不相交的树的集合。、

    二叉树:二叉树(binary tree,简写成BT)是一种特殊的树型结构,它的度数为2的树。即二叉树的每个结点最多有两个子结点。每个结点的子结点分别称为左孩子、右孩子,它的两棵子树分别称为左子树、右子树。
    二叉树性质:
    1.  在二叉树的第i层上最多有2^(i-1)个结点(i>=1)。
    2.  深度为k的二叉树至多有2^k –1个结点(k>=1)。
    3.  对任意一棵二叉树,如果其叶结点数为n0,度为2的结点数为n2,则一定满足:n0=n2+1。
    4.  具有n个结点的完全二叉树的深度为floor(log2n)+1
    5.  对于一棵n个结点的完全二叉树,对任一个结点(编号为i),有:
        ①如果i=1,则结点i为根,无父结点;如果i>1,则其父结点编号为i/2。   如果2*i>n,则结点i无左孩子(当然也无右孩子,为什么?即结点i为叶结点);否则左孩子编号为2*i。
        ②如果2*i+1>n,则结点i无右孩子;否则右孩子编号为2*i+1。

    堆:堆结构是一种数组对象,它可以被视为一棵完全二叉树。树中每个结点与数组中存放该结点中值的那个元素相对应。
    堆的性质:堆具有这样一个性质,对除根以外的每个结点i,A[parent(i)]≥A[i]。即除根结点以外,所有结点的值都不得超过其父结点的值,这样就推出,堆中的最大元素存放在根结点中,且每一结点的子树中的结点值都小于等于该结点的值,这种堆又称为“大根堆”;反之,对除根以外的每个结点i,A[parent(i)]≤A[i]的堆,称为“小根堆”。

图论算法

    图:点用边连接起来就叫做图,严格意义上讲,图是一种数据结构,定义为:graph=(V,E)。V是一个非空有限集合,代表顶点(结点),E代表边的集合。
    图论是我们学习的最后一个专题,图论中学习了很多的图,有向图,无向图,完全图,包括一些线性结构,并查集,最小生成树,单源最短路径,还有一些经典的算法问题,Prim,kruskal,Dijkstra等算法,图论在离散中学习的都很像,在这一学期中数据结构涉及的很深,给出了很多的代码跟思想,一直寻找最快速的方法。
    图论中的一些定义:
        1.  结点的度:无向图中与结点相连的边的数目,称为结点的度。
        2.  结点的入度:在有向图中,以这个结点为终点的有向边的数目。
        3.  结点的出度:在有向图中,以这个结点为起点的有向边的数目。
        4.  权值:边的“费用”,可以形象地理解为边的长度。
        5.  连通:如果图中结点U,V之间存在一条从U通过若干条边、点到达V的通路,则称U、V 是连通的。
        6.  回路:起点和终点相同的路径,称为回路,或“环”。

    我的感觉就是图论这部分特别难,倒不是理解起来费力,而是应用起来完全无从下手……所以总结的很少,当然之前的总结也不是多完善。

总结

    ACM课程到现在已经结束了,第一堂课看到费老师的兴奋和对ACM的憧憬、做一道题却一直WA的烦躁以及最后AC时候的喜悦、费老师的名言:“生死看淡,不服就干~”……这些都还历历在目。ACM课程虽然已经结束了,但学习还远没有结束。ACM课程让我意识到自己现在所接触到的只是知识大海的冰山一角,还有大把知识供我学习、领悟,我们要做的就是虚心求教,沉心学习。任何时候都要记得:人外有人,天外有天。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值