标签:
一.dfs序在树状图中的经典应用
首先是dfs序的问题,什么是dfs序?
我的理解:dfs序也就是将一棵树通过树的遍历顺序将一棵树转化为父节点包含了子节点的序列,n个结点的树对应n个数的序列,一个结点在序列中的表现形式为一段区间,这段区间中包含了该结点的子树区间,且构造的区间只包含不相交。
简而言之dfs序能够完美解决树状图的结点映射问题。
dfs序代码
· void dfsx(int k,long long sum)
· {
· vis[k]=1;
· tot++;
· in[k]=tot;
· dis[tot]=sum;
· for(int i=0; i<v[k].size(); i++)
· {
· if(!vis[ v[k][i] ]) dfsx(v[k][i],sum+a[ v[k][i] ]);
· }
· out[k]=tot;
· }
可以看到,在dfs序的建立过程中,我们将一个结点分为了一个左区间in,和一个右区间out,在每个结点的in和out区间之中,又是根据遍历的顺序去包含它的子树区间,代码清晰易懂。
这里需要注意一点,每个结点的in端点是该结点的关键结点,没有任何其他结点的in端点与之重合,而out则不然。
举个例子 ,假设我们有这样一棵树
在运行完成上述代码之后,我们可以得到每个结点的这样一个区间
In out
1 1 7
2 2 4
3 5 7
4 3 3
5 4 4
6 6 6
7 7 7
完美的将点转换为区间。
那么,基于dfsx这样的一个特性,能够解决哪一类问题?
有这样一类问题,dfsx非常好处理,即对一棵树的某个结点的子树进行更新,求和。
乍一看这样的问题似乎很难,然而如果此时树转dfsx,然后再利用其他数据结构(线段树,树状数组,etc)去维护这个dfsx,问题就变得轻而易举了。
Dfs序的介绍到此结束。
上一道经典题目
题目链接:
题目分析:
这道题目如何用dfs序来解决,首先直观的发现我们每一次的询问无非就是在查询一个结点和这个结点的子树到达根节点的最优距离,而修改操作无非就是在一个 结点上维护这个最优距离,映射到dfs序上来解释:查询就是在查询某个区间内的最优值,更新操作就是在修改某个区间的最优值,有了这些理论,我们就能很快 的想到用线段树或者其他数据结构去维护这样一个序列,此时问题得以解决。
详解及源代码:
最近一场比赛刚刚考到这中题目,当时知道是双向广度优先搜索,但是由于之前只是了解却没有搞过,所以没有想到如何去处理,觉着这个东西还是有必要去摸索一下的。
什么是双广?
我的理解:
双广即同时考虑初始状态和终止状态,让这两个状态同时行动,最终的结果是两种状态相遇,此时问题得以解决。
相对与单向广搜,双广的搜索范围更小,耗时更少。
双广具体操作:
1.将初始状态和终止状态同时扔进队列
2.保存两种状态对应的哈希映射
3.当两种状态的映射有交集时得到答案,退出
代码核心:
Que,push(q1);
Que.push(q1);
While(!Que.empty())
{
Mp1 && Mp2 intersection? YES-> break;
Solve();
}
初学者可以先尝试用双广做一下这道题:
有了这道题的基础再来考虑比赛的题目:
题目分析:
如果单向搜索,无论是从终状态还是从开始状态都会直接超时,计算次数为4^20 > 10^8
那么此时应该考虑双向搜索,即队列中同时保存终状态的状态转移和开始状态的状态转移,每一种状态bfs10步,当两端相撞时得到结果,计算次数 4^10
考虑数字不多,直接用map做状态映射。
详解及源代码:
对于kth number这个问题,已经是烂大街了,最常说的解法就是各种树,什么树套树啊,主席树啊,划分树啊...总之是一大堆的树去解决,然而本身用树去解决是没有问题的,但是一旦涉及到树去解决,带来的也必然是大量的代码和不够优美的空间,这里介绍一种在时间和空间以及代码实现上都较为优秀的思想--整体二分。
什么是整体二分?
我的理解:
整体二分也是一种二分的思想,只是二分的对象不再仅仅是答案,而是一个整体,拿kth number问题来说,我们要二分的不仅仅是ans,还有整个查询与修改操作。对于一次询问来说,我们可以二分答案,逐步缩小范围直到得到解,对于多个询问也是如此,当我们二分答案时,会发现一些询问的答案应该小于mid,一些应该大于mid,以此为条件,就可以将询问划分为两类,当我们二分答案时,这些询问也随之二分,递归解决这些询问,当然每一次都要消除左递归对右递归的影响,以此二分到能够解决问题的程度。
对此,在应用整体二分思想时应该保证我们处理的整体是离线的,并且每一次的修改或查询应当相互独立,且答案可二分,如果题目强制要求在线算法,那么整体二分就不适用了。
引用一下XHR的论文
整体二分这个东西在理解上要求比较高,理解起来要有耐心。
推介一篇博客去看一下:
新手入门题:
详解及源代码:
HDU 5412 CRB and Queries
详解及源代码:
详解及源代码:
兔子朝着比现在高的地方跳去。他们找到了不远处的最高山峰。但是这座山不一定是珠穆朗玛峰。
兔子喝醉了。他随机地跳了很长时间。这期间,它可能走向高处,也可能踏入平地。但是,他渐渐清醒了并朝最高方向跳去。
兔子们吃了失忆药片,并被发射到太空,然后随机落到了地球上的某些地方。他们不知道自己的使命是什么。但是,如果过几年就杀死一部分海拔低的兔子,多产的兔子们自己就会找到珠穆朗玛峰。
兔子们知道一个兔的力量是渺小的。他们互相转告着,哪里的山已经找过,并且找过的每一座山他们都留下一只兔子做记号。他们制定了下一步去哪里寻找的策略。
以上四个方法分别对应了局部搜索,模拟退火,遗传算法,搜索禁忌。
引用一下百度百科:
“模拟退火”算法是源于对热力学中退火过程的模拟,在某一给定初温下,通过缓慢下降温度参数,使算法能够在多项式时间内给出一个近似最优解。退火与冶金学上的‘退火’相似,而与冶金学的淬火有很大区别,前者是温度缓慢下降,后者是温度迅速下降。
模拟退火这个东西直白的来说是一种变步长的贪心算法,举个简单的例子来说,如果我要找到一个凹函数的极点,我们该怎么做?大多数人很容易想到直接对函数范围三分或者对函数求导再用二分去找,现在我们仔细分析一下三分处理过程,每一次我们三分时,我们是不是都是在以1/3为界限逐步的缩小我们的取值范围,我们每一次都将范围缩小了1/3或者2/3,并且在缩小时保证了我们缩小的那个边界点是更接近我们要找的答案的,现在假设我们抛开1/3这个边界点,既然1/3能够为界,那么说明1/4、1/5、1/6...也是可行的,只不过按照三分规则在实现上略为复杂,还是从原理出发,考虑我们只要保证每一次的缩小范围都是在更接近答案,那么我们就一定能在有限时间内找到答案,这样就好办了:
我们假设初始的点为x,向左或者右找x±d 下标对应的函数值,再取更接近答案的点y=x+d或者x-d,再次寻找以d为下降坡度的最优点,一直到没有更优点结束,现在使坡度减小d=d*delte,再次找最优点,一直到d达到题目要求的精确范围。
这就是模拟退火的整个过程,核心就在于变步长的贪心。
代码实现:
While(d>eps)
{
While(未找到最优解)
{
向四周查找更优解;
}
d*=delte; 变步长,减小精度
}
模拟退火整个讲解结束。
题目:
详解及源代码:
详解及源代码:
题意:给出平面坐标内的一些点,要求你在坐标平面内找到一点使所有点到该点的距离和最小,输出这个距离
详解及源代码:
题意:给出三维空间的一些点,要求你找到三维空间的一个点使这个点形成的球包含所有的点并且半径最小,输出这个半径
详解及源代码:
题意如下:
一个人拿到了他祖父的遗产——一个凸多边形的农场。原本地皮上用来划分地界用的那些绳子和一部分的钉子不知去向。那么是否能通过剩下的钉子来判断原本的土地呢?
首先,由题意可知,这张图是一个凸包。然后我们的任务是判断这个凸包是否能确定的表示一块土地,即:这些凸包上的点与任意一个凸包外的点,(在点全部使用的情况下)无法形成新的凸包。
那么,我们假定有这么一条边AB。
因为所有的点都要作为凸包上的点,因此若要添加新点,则需要在不影响AB以及其他点的情况下添加。由凸包的性质可知在区域ABC内添加。必然满足条件。
那么,我们只要把这个区域给消除掉就好了。
因此可以得出结论:对于任意一条边,至少有一条临边与之共线。
那么接下来就好做了嘛,其实是一些细节需要注意,因为凸包的排序给的是所有的点按照逆时针+由近到远,而我们的排序在最后一条直线的地方是要由远到近所以在凸包排完之后把最后几个加进来就好了。。。除了起始和最后以外不可能存在共线所以没问题。
啊,最后如果只有2个点共线的情况原本应该是要特判的,然而出题人没考虑这样的数据吧?最开始没加也过了。。。不过严谨一点总没有错
源代码:
题目传送门
树的重心,嘛,就是说一个类似于一个稳定的中点(几何没学好概念忘记啦OvO)。在树上的定义也差不多,意思是该树的子树中结点数最多的节点数最少,这句话多读几遍应该能理解=w=
要找树的重心,我们需要知道它的所有的直接子树上的节点数目,这一点用Dfs便能轻松实现,然而如果是一棵无根树,那么对每个点进行查找的时候就会产生重 叠,导致复杂度升高。所以我们将其看作一棵有根树,然后从根节点开始查找所有的子节点的子树(递归实现),父节点的子树则用总点数减去(该点子节点 和+1)来判断。
源代码:
长为m的单词,要求相邻字母的ascii码值相差小于等于32且至少有一对相邻的字母,码值相差恰好为32,求这样的单词有多少个,其中m<1e9。
f(i,j)表示长为i,以j为结尾字母且所有相邻单词之差小于32的单词个数,f(i,j)=∑f(i,k)(k与j的差值小于32) ;g(i,j)表示长为i,以j为结尾字母且所有相邻单词之差小于或等于32的单词个数,g(i,j)=∑g(i,k)(k与j的差值小于等于32)。可以用矩阵快速幂构造转移矩阵,用快速幂求解。
源代码
感谢wjs撰写本文,感谢wjs,wrx,hwf提供解题报告及源代码
标签:
原文地址:http://blog.csdn.net/forever_wjs/article/details/51866608