前言:
“紧张刺激”的大一下学期马上就要结束了!从最初接触C++到现在也已经快要有1年的时间了。在大一上学期,c++课上学的基础知识大部分都是属于c语言的,为了熟练掌握这些基础知识,费老让我们做了很多的练习题。其实,我觉得,这些练习题也可以算是acm的范畴,只不过这些题实在是简单到不能再简单了。到了这一学期,或是说在寒假里,我才真正的知道了ACM题是有多么的困难!!说实话,在上学期做题做的挺轻松地,那是也觉得acm再难还能难倒那里去,于是我就跟随着远飞的脚步,没有犹豫的选修了ACM程序设计这门课,结果,专题一就给我们了一个下马威!
专题一的内容是贪心算法,对于刚开始接触acm的我们来说算是比较困难的。不过当时做的还真挺带劲,当时的课程挺多的,好像只周三下午能拿出比较长的时间然后泡在机房里做道题,做出一道题来需要好长一段时间,不过能做出来就很高兴了。但是问题也随之而来,学习acm需要的时间太多了,并且好要耗费大量的精力,而我这个人最大的缺点就是没有毅力,所以慢慢的,对ACM的学习也就懈怠了,不是特别的把它放在心上。
专题二的内容是深度搜索和广度搜索,这一个专题没有学好,知识简简单单的知道什么时候该用什么搜索,但题目做的太少,实践不够,然后居然没有得分。。
专题三是动态规划,这个专题还不错,学到了不少知识,但是关于地图的问题不怎么会。
专题四是关于“图”的,各种图都把我搞晕了。这个专题是难度最高的一个专题,但是这个专题的知识点可以用来解决很多的实际问题,比如,我们现在的课程设计作业中有个12306的程序,在查询车次的功能中可以用到它,再者,我觉得,电脑游戏里的自动寻路功能就和这些算法(最小生成树,并查集等)有关,所以这部分非常的重要,虽然现在没有搞明白,但是以后必须要学会!!
知识点:
然后是acm中的知识点,一下的内容以课件为主:
一、Stl基本知识:
1、 stack是一种先进后出(First In Last Out, FILO)的数据结构,它只有一个出口,只能操作最顶端元素。
头文件: #include <stack>
定义:stack<data_type> stack_name;
如:stack <int> s;
操作:
empty() -- 返回bool型,表示栈内是否为空
(s.empty() )
size() -- 返回栈内元素个数 (s.size() )
top() -- 返回栈顶元素值 (s.top() )
pop() -- 移除栈顶元素(s.pop(); )
push(data_type a) -- 向栈压入一个元素 a(s.push(a); )
2、 queue是一种先进先出(First In First Out, FIFO)的数据结构,从底端加入元素,从顶端取出元素
头文件: #include <queue>
定义:queue <data_type> queue_name;
如:queue <int> q;
操作:
empty() -- 返回bool型,表示queue是否为空 (q.empty() )
size() -- 返回queue内元素个数 (q.size() )
front() -- 返回queue内的下一个元素 (q.front() )
back() -- 返回queue内的最后一个元素(q.back() )
pop() -- 移除queue中的一个元素(q.pop(); )
push(data_type a) -- 将一个元素a置入queue中(q.push(a); )
3、 upper_bound 和 lower_bound
upper_bound(begin, end, value);
返回可插入值为value的元素的第一个位置。
Lower_bound(begin, end, value);
返回可插入值为value的元素的最后一个位置。
num[] = {1,2,2,3,4,5};
lower_bound(num, num + 6, 2)为num + 1
upper_bound(num, num + 6, 2)为num + 3
4、map和multimap
m.size() 返回容器大小
m.empty() 返回容器是否为空
m.count(key) 返回键值等于key的元素的个数
m.lower_bound(key) 返回键值等于key的元素的第一个可安插 的位置
m.upper_bound(key) 返回键值等于key的元素的最后一个可安 插的位置
m.begin() 返回一个双向迭代器,指向第一个元素。
m.end() 返回一个双向迭代器,指向最后一个元素的下一个 位置。
m.clear() 讲整个容器清空。
m.erase(elem) 移除键值为elem的所有元素,返回个数,对 于map来说非0即1。
m.erase(pos) 移除迭代器pos所指位置上的元素。
直接元素存取:
m[key] = value;
查找的时候如果没有键值为key的元素,则安插一个键值为key的新元素,实值为默认(一般0)。
m.insert(elem) 插入一个元素elem
a)运用value_type插入
map<string, float> m;
m.insert(map<string, float>:: value_type ("Robin", 22.3));
b) 运用pair<>
m.insert(pair<string, float>("Robin", 22.3));
c) 运用make_pair()
m.insert(make_pair("Robin", 22.3));
二、贪心:
1、什么是贪心算法呢?
在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,直接去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解,这种求解方法就是贪心算法。
从贪心算法的定义可以看出,贪心算法不是从整体上考虑问题,它所做出的选择只是在某种意义上的局部最优解,而由问题自身的特性决定了该题运用贪心算法可以得到最优解。
2、怎样使用贪心算法?
如果一个问题可以同时用几种方法解决,贪心算法应该是最好的选择之一。
也就是说,贪心算法的基础就是化整体为部分,以每一部分的最优解来得出整体的最优解。虽然贪心算法是一种十分简洁的方法,但是并不能保证对所有的问题都有效,所以利用贪心策略解题,需要解决两个问题:
(1)该题是否适合于用贪心策略求解;
(2)如何选择贪心标准,以得到问题的最优/较优解。
3、举例:
贪心算法中比较经典的例题应该算是时间安排类型的题吧,然后这个基本的问题会延伸出许多其他类型的题目,比如说专题一中第一道搬桌子的题:
在专题一中,第一道搬桌子(ACM公司一条过道南北两面各200个房间,把桌子从一个房间搬到另一房间,每次每段过道只能搬一次桌子,用时10分钟。搬n次桌子,求最短时间)的题吓了我一跳,那是第一次用贪心算法,还是很不熟悉,自己以为已经理解了在课上学的东西,但是在实际操作上却犯了难,还是反映了自己对贪心算法的理解并不深刻。第一遍读这个题时,感觉它很容易理解,所以写出的代码十分的无脑,之后又读了好几遍题目终于理解了一部分,但是有个难点随之出来,一直不会处理它,纠结了老长一段时间,就去参考了一下袁飞的代码,发现他设置了一个非常神奇的筛选变量,接着我又把课件拿出来复习了一下,之后算是才明白了怎样用贪心算法进行时间的安排。然后下面是思路:
然后是我的思路,共有400间房间,但因为它们分别在走廊的两侧,所以在走廊同一位置的南北两间房间可也看做成一间,于是可以把这400间房间重新编号成200间。首先对输入各组的数据按小号房间号进行排列,在输入一组数据时要注意小房间在前,大房间在后,每个房间号减1除以2来生成新的房间号,之后便可以用贪心的算法进行求解了。在求解时设置了一个筛选变量,来对各组数据进行多次筛选。首先第一遍筛选,将没有相交的房间组进行筛选,并将筛选变量归零,以此来控制下次筛选时不对已筛选完成的房间组进行操作,用时10分钟;第二遍筛选将剩下的房间组再进行选择,继续选出不相交的房间组,用时10分钟,然后继续第三次筛选直至房间组全部筛选完成。最后输出,然后结束。总体来说就是对所有的情况进行多次筛选,每次都筛选出最符合条件的情况,直到将所有的情况筛选完成。
三、二分法搜索算法、三分基础:
1、二分:
简单定义:在一个单调有序的集合中查找元素,每次将集合分为左右两部分,判断解在哪个部分中并调整集合上下界,重复直到找到目标元素。
最早接触二分法是在数学课上的求根问题,当时老师开玩笑的跟我们说,你们学计算机的应该编个二分法的程序试一试,然后,现在就真的用到了。
核心代码(模版):
long long res,mid;
while (low <= high)
{
mid = (high + low) / 2;
if (judge(mid) )
{
low = mid + 1;
res = mid; //最后结果为res
}
else
{
high = mid - 1;
}
bool judge(long long mid)
{
long long p = 0;
for (int i = 0; i < n; ++i)
{
p += size[i] / mid;
}
return p >= f;
}
对于二分法算法,只要记住了模版,基本上问题就解决了;
2、三分:
当需要求某凸性或凹形函数的极值,通过函数本身表达式并不容易求解时,就可以用三分法不断逼近求解。
三分法其实就是用了两个二分法,在mid和left或right中间又取了一个中间值:
类似二分的定义Left和Right
mid = (Left + Right) / 2
midmid = (mid + Right) / 2;
如果mid靠近极值点,则Right = midmid;
否则(即midmid靠近极值点),则Left
= mid;
核心代码(模版):
double mid, midmid;
while ( low + eps < high )
{
mid = (low + high) / 2;
midmid = (mid + high ) / 2;
double cmid = cal(mid);
double cmidmid = cal(midmid);
if ( cmid > cmidmid )
high = midmid;
else
low = mid;
}
double cal(double x)
{
return (h * D - H * x) / (D - x) + x;
//这里放要求的函数;
}
四、搜索:
1、广度搜索:
基本思想:从初始状态S 开始,利用规则,生成所有可能的状态。构成的下一层节点,检查是否出现目标状态G,若未出现,就对该层所有状态节点,分别顺序利用规则。生成再下一层的所有状态节点,对这一层的所有状态节点检查是否出现G,若未出现,继续按上面思想生成再下一层的所有状态节点,这样一层一层往下展开。直到出现目标状态为止。
具体过程:
1 每次取出队列首元素(初始状态),进行拓展
2 然后把拓展所得到的可行状态都放到队列里面
3 将初始状态删除
4 一直进行以上三步直到队列为空。
2、深度优先:
基本思想:从初始状态,利用规则生成搜索树下一层任一个结点,检查是否出现目标状态,若未出现,以此状态利用规则生成再下一层任一个结点,再检查,重复过程一直到叶节点(即不能再生成新状态节点),当它仍不是目标状态时,回溯到上一层结果,取另一可能扩展搜索的分支。采用相同办法一直进行下去,直到找到目标状态为止。
具体实现过程
1 每次取出栈顶元素,对其进行拓展。
2 若栈顶元素无法继续拓展,则将其从栈中弹出。继续1过程。
3 不断重复直到获得目标状态(取得可行解)或栈为空(无解)。
五、动态规划:
什么是动态规划?
(一)动态规划是解决多阶段决策问题的一种方法。
多阶段决策问题:如果一类问题的求解过程可以分为若干个互相联系的阶段,在每一个阶段都需作出决策,并影响到下一个阶段的决策。多阶段决策问题,就是要在可以选择的那些策略中间,选取一个最优策略,使在预定的标准下达到最好的效果.
个人觉得动态规划就是贪心和搜索的综合,将整个问题的解决过程分为若干阶段,每个阶段都是在前一阶段的基础上选择最优决策,进而影响到最终结果的选择。
2 最优性原理:
不论初始状态和第一步决策是什么,余下的决策相对于前一次决策所产生的新状态,构成一个最优决策序列。最优决策序列的子序列,一定是局部最优决策子序列。包含有非局部最优的决策子序列,一定不是最优决策序列。
2 动态规划的指导思想:
在做每一步决策时,列出各种可能的局部解,依据某种判定条件,舍弃那些肯定不能得到最优解的局部解,以每一步都是最优的来保证全局是最优的。
2 动态规划的基本模型:
1、问题具有多阶段决策的特征。
2、每一阶段都有相应的“状态”与之对应,描述状态的量称为“状态变量”。
3、每一阶段都面临一个决策,选择不同的决策将会导致下一阶段不同的状态。
4、每一阶段的最优解问题可以递归地归结为下一阶段各个可能状态的最优解问题,问题与原问题具有完全相同的结构。
2 动态规划的几个概念:
阶段:据空间顺序或时间顺序对问题的求解划分阶段。
状态:描述事物的性质,不同事物有不同的性质,因而用不同的状态来刻画。对问题的求解状态的描述是分阶段的。
决策:根据题意要求,对每个阶段所做出的某种选择性操作。
状态转移方程:用数学公式描述与阶段相关的状态间的演变规律。
状态转移方程是整个动态规划中最重要的一部分,整个代码的核心就是这一部分,该方程起到的作用就是某个阶段的决策,即选择作用,选择最适合这一阶段的决策。
2 动态规划问题的一般解题步骤
1、判断问题是否具有最优子结构性质,若不具备则不能用动态规划。
2、把问题分成若干个子问题(分阶段)。
3、建立状态转移方程(递推公式)。
4、找出边界条件。
5、将已知边界值带入方程。
6、递推求解。
(二)动态规划实际上就是一种排除重复计算的算法,更具体的说,动态规划就是用空间换取时间。
动态规划有个很经典的例子,那就是斐波那契数列。在学acm之前做过几个关于斐波那契数列的题目,当时觉着这类题太神奇了,只要有一个方程,所有的结果就都出来了,然后现在终于明白其中的原理了,拿上楼梯的问题“有一楼梯共M级,刚开始时你在第一级,若每次只能跨上一级或二级,要走上第M级,共有多少种走法?”来举例,想要走到第n层,只要在n-1层上迈一步,在n-2层上迈两步,所以总的结果就是走到n-1层的走法和走到n-2层的走法只和,然后结果就出来了,所以他的动态转移方程就是:dp[n]=dp[n-1]+dp[n-2]。
六、图:
最后的一个专题是关于图的,这个专题对我来说有点困难,只是理解了很少很少的一部分,所以在这就不在写了。
小结:
Acm的学习之路是充满荆棘的,但是我因为自己的惰性,失去了一个很大的实现自己价值的机会,虽然acm学的不是那么好,但还是有所收获!以后继续努力!
注:知识点取自于费老的课件。