ACM课程总结
没想到ACM程序设计基础课这么快就结束了,虽然是短短的十几周课可令我受益匪浅,无论是代码能力还是逻辑思维都得到了很大的锻炼,想起之前选课前的担忧与不自信,现在只有庆幸,庆幸我选择了ACM这条大船。
ACM即acm国际大学生程序设计大赛,是由国际计算机学会主办,一项旨在大学生创新能力,团队精神和压力下编写程序,分析解决问题能力的年度竞赛。经过近三十多年的发展,acm国际大学生生程序设计竞赛。赛事目前由IBM赞助。
这个学期共学了STL的应用,递归递推算法,动态规划,二分贪心算法,搜索及树和图论算法。
Acm一:c++模板库
一个方便的的工具库,许多方便的容器与函数让算法实现变得更加的方便,代码看起来更加的简洁,更帅。
像常见的容器:栈
Acm二:递归递推
递归算法是把问题转化为规模缩小了的同类算法的子问题,然后递归调用函数,而递归题的关键就是找出大规模问题与小规模问题的关系,从而使问题规模逐渐变小,当然在问题被降到最小规模时设置好边界使其结束。而递推算法则是通过已知条件,利用特定关系得出中间结论,最终当问题被递推到最后时的出最终答案。比如就是已知f[0],与f[i]与f[i-1]的关系,那么这样就可以推出f[n]。而递推分为顺推和反推,通过已知条件决定顺推还是反推。
递归地推属于基本算法,可以说是后面的基础,像动态规划就是要利用递归与递推来完成,还有后面的深搜几乎就是递归等等。
Acm三:动态规划
说道动态规划这就是一道大菜了,所谓动态规划,就是先取局部最优解,最后得到全局最优解。或者是,先求当前阶段的最优解,最后得到全部结束后的最优解。
在求局部最优解的时候,也不能仅仅着眼于局部,需要考虑全局,要符合全局目标和全局条件来求局部最优。
做好动态规划其中最关键的一步是找到状态转移方程,也就是两个状态之间的关系,而且要保证状态的转移是朝着问题解决的方向上,当然还要确定好开始与结束。
动态规划解题步骤:
1, 判断问题是否具有最优子结构性质,只有具有一层一层的最优子结构,才能完成状态的逐步转移。
2, 把问题分成若干子问题,分出阶段。
3, 推出状态转移方程。
4, 找出边界条件。
5, 将边界带入方程。
6, 递推求解。
而动态规划问题中哟一类经典问题,那就是背包问题。
看例题:求最长上升子序列
一个数的序列bi,当b1< b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1,a2, ..., aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7),(3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1,3, 5, 8).
本题是课上基础题之一,思路是:设f[i]是数列到第i位时最长上升数列个数,那么它必等于前i-1位中f的最大值加当前一位,当然数列前面的数要小于i位上的数。
inta[1001]={0,1},b[1001]={0},maxd=0,maxe=0,n;
cin>>n;
for(inti=1;i<=n;i++)
{cin>>b[i];}//储存数
for(inti=2;i<=n;i++)
{
for(intj=1;j
{
if(b[i]>b[j])//前面的数要小
{if(a[j]>maxd){maxd=a[j];}}
}
a[i]=maxd+1;maxd=0;//累加当前满足条件的一位
}
for(inti=1;i<=n;i++)
{if(a[i]>maxe){maxe=a[i];}//把最长的截止位中长度取出}
cout< } 1,01背包 这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。 用子问题定义状态:即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是: f[i][v]=max{ f[i-1][v], f[i-1][v-w[i]]+v[i] }。 可以压缩空间,f[v]=max{f[v],f[v-w[i]]+v[i]} 例题: 电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。 Input 多组数据。对于每组数据: 第一行为正整数n,表示菜的数量。n 第二行包括n个正整数,表示每种菜的价格。价格不超过50。 第三行包括一个正整数m,表示卡上的余额。mn=0表示数据结束。 Output 对于每组输入,输出一行,包含一个整数,表示卡上可能的最小余额 递推式:dp[i][j]=max(dp[i-1][j],dp[i-1][j-price[i]]+price[i]; Dp[i][j]表示前i道菜放j元背包最多花的钱 int cmp(int a1,int b1) { return a1 int m,i,j,max1; int price[2007]= {0},dp[2007]={0}; for(i=1; i<=n; i++) cin< sort(price+1,price+1+n,cmp); max1=price[n]; cin< if(m<5) {cout< m=m-5; for(i=1; i for(j=m; j>=price[i]; j--) {dp[j]=max(dp[j],dp[j-price[i]]+price[i]); } Cout< } 2,完全背包 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是c,价值是w。将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。 考虑到第i种物品最多选V/c 件,于是可以把第i种物品转化为V/c件体积及价值均不变的物品。for i=1..N for v=0..Vf[v]=max{f[v],f[v-c]+w}; 3,多重背包 有N种物品和一个容量为V的背包。第i种物品最多有n件可用,每件体积是c,价值是w。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。 这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n+1种策略:取0件,取1件……取 n件。令f[v]表示前i种物品恰放入一个容量为v的背包的最大权值,则:f[v]=max{f[v-k*c]+k*w|0<=k<=n}。 4,混合三种背包问题 有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。 那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可 5,二维背包。 二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为a和b。两种代价可付出的最大值(两种背包容量)分别为V和U。物品的价值为w。 费用加了一维,只需状态也加一维即可。设f[v]表示前i件物品付出两种代价分别为v和u时可获得的最大价值。状态转移方程就是:fi[i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i]}。如前述方法,可以只使用二维的数组:当每件物品只可以取一次时变量v和u采用逆序的循环,当物品有如完全背包问题时采用顺序的循环。 Acm三:二分查找 其实二分查找是一种套路,当数据量比较大是,二分查找前数据最好排好序。 (1),确定区间中间位置(k)。(2)将其值,与begin值与end值进行比较,若相等,查找完毕,否则确定新的区域,继续二分查找。区域变化array[k]>T知array[k,k+1,……]>T故新区间array[beg,……k-1],反之在另一边。 二分法最大好处是将算法的时间的复杂度降低复杂度T(n)=O(logn) 例题: 先输入两数,表示牛舍数与要放下多少头牛,后面给出牛舍的位置(牛舍在一条直线),求出放完牛后有牛的牛舍之间最小距离中的最大值 分析: 一定数量的牛舍,一个位置只能放一头牛,那么要放入的牛数量越多,牛舍之间距离相对越小,所以可以用二分法做,我用judge函数判断x距离下能否放下这C头牛,二分循环找出可以放下这C头牛的最大的两舍之间的最小距离。 以下是二分法查找的套路解法 int m,n,a[100002]; bool judge(int x) { int g=a[0],f=1; for(int i=0;i { if(a[i]-g>=x) { f=f+1;//符合距离要求,放下一头牛 g=a[i]; if(f>=m){return true;}访完了,可以 } } return false; } int main() { int k,max=-1,ans=0,beg,end,mid; scanf("%d%d",&n,&m); for(int i=0;i {scanf("%d",&a[i]); if(a[i]>max){max=a[i];} } sort(a,a+n); end=max-a[0];beg=1; ans=1; while(beg<=end)//逐步找出符合要求的最小距离,在诸多都可以放下C头牛的最小距离中找出最大值 { mid=(end+beg)/2; if(judge(mid)) {ans=mid;beg=mid+1;} else {end=mid-1;} } cout< } Acm四:贪心算法 贪心算法,就是在求最优解问题中一句贪心的标准,从问题的初始状态出发,直接去求每一步的最优解,通过若干次的贪心选择,最终求出最优解。 贪心算法只对某一类问题可以求出最优解,关键是贪心策略的选择,选择贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,至于当前状态有关。 贪心算法的核心其实就是一种思想比如说像性价比问题这类。无非先拿性价比最高的之类的。 工厂生产奶酪,每天都有定额上交,每天的生产成本都不同,完成生产量,但明天的上交量可以在今天生产好保存,但保存要耗费成本,试计算法,求怎样安排生产最省钱。 思路: 就是要比较今天要生产的单价在与在今天之前生产的单价累加保存天数作比较,选小的数累加计算花费; 其实就是贪心思路做的 int main() { long longcos=0,p=999999,s,n,a,b; cin>>n>>s; for(inti=0;i { cin>>a>>b; p=min(a,p+s);//比较今天的任务是今天生产还是昨天(或更早)生产 cos=cos+p*b; } cout< } Acm五:搜索 所谓搜索算法,就是在解空间里进行遍历,找出答案。 搜索又分为深搜和广搜。 深搜:即深度优先搜索,始终对下一层的优先进行搜索,后面层数的结点便利过后,再返回上一层,逐层返回,知道找到答案,深搜强调的十全,即所有结点将至少遍历一遍。因此,深搜往往适用于一些“所有”类型的问题。 广搜:即广度优先搜索,往往利用队列,始终遍历前一层的结点,当前一层结点全部遍历过后在遍历下一层结点,当发现所需目标时,立即结束搜索,不在进行下一层的遍历,,因此,广搜往往适用于“最”类型的题目。 其实我感觉搜索的题就解题方法都非常套路化。 广搜例题: int bfs() { queue que.push(Q); while(!que.empty()) { temp=que.front(); for() {对进行分析分成若干元素依次放进que里} que.pop(); } returnsum; } Acm六:树 到这,就进入到了数据结构部分,这个部分是最后学的。所以对这个专题还没进行足够的训练。 1.树的定义 树的递归定义: 树(Tree)是n(n≥0)个结点的有限集T,T为空时称为空树,否则它满足如下两个条件: (1)有且仅有一个特定的称为根(Root)的结点; (2)其余的结点可分为m(m≥0)个互不相交的子集Tl,T2,…,Tm,其中每个子集本身又是一棵树,并称其为根的子树(Subree)。 注意: 树的递归定义刻画了树的固有特性:一棵非空树是由若干棵子树构成的,而子树又可由若干棵更小的子树构成。 (3)定义一棵树的根节点的层次为0. (4)森林是m棵互不相交的树的集合。 2树的存储结构; (1) 父亲表示法。(2)孩子表示法。(3)父亲孩子表示法。(4)孩子兄弟表示法。 3,树的遍历 (1) 先序根遍历。(2)后序根遍历(3)层次遍历(4)叶节点遍历. 4,二叉树。 在计算机科学中,二叉树是每个结点最多有两个子树的有序树。通常子树的根被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用作二叉查找树和二叉堆或是二叉排序树。 二叉树的每个结点至多只有二棵子树(不存在度大于2的结点),二叉树的子树有左右之分,次序不能颠倒。二叉树的第i层至多有2的 i -1次方个结点;深度为k的二叉树至多有2^(k) -1个结点;对任何一棵二叉树T,如果其终端结点数(即叶子结点数)为n0,度为2的结点数为n2,则n0 = n2 + 1。 树的递归定义如下:(1)至少有一个结点(称为根)(2)其它是互不相交的子树。 1.树的度——也即是宽度,简单地说,就是结点的分支数。以组成该树各结点中最大的度作为该树的度,如上图的树,其度为2;树中度为零的结点称为叶结点或终端结点。树中度不为零的结点称为分枝结点或非终端结点。除根结点外的分枝结点统称为内部结点。 2.树的深度——组成该树各结点的最大层次。 3.森林——指若干棵互不相交的树的集合,如上图,去掉根结点A,其原来的二棵子树T1、T2、T3的集合{T1,T2,T3}就为森林; 4.有序树——指树中同层结点从左到右有次序排列,它们之间的次序不能互换,这样的树称为有序树,否则称为无序树。