算法的精髓就差和搜,并很简单。
这个差和搜中搜的最为巧妙,可以用路径压缩,路劲压缩有用到dfs+dp的思想,其实就是把每个节点的父亲都变为始祖,
这个下次搜索的时候就不用递归搜,直接可以得到始祖,节省了非常多的时间,对于大多数题目不路径压缩基本超时(大多数提示不是指基础题),之前我想过了一种减少路径长度的方法,感觉效果一样,但是对于一些其他的操作就有点力不从心了。
我的路劲压缩:
void merge(int r1,int r2)
{
if(parent[r1]>parent[r2])
{
parent[r1]=r2;
parent[r2]-=parent[r1];
}
else
{
parent[r2]=r1;
parent[r1]-=parent[r2];
}
}
我的路径压缩时在处理集合并的时候做的。
原理:将每个节点父亲数组的值赋值为-1在每次v并的时候判断那个父亲值的绝对之小,把值大的作为父亲然后执行if 中的第二条语句,这样就间接的把一颗树中点的个数记录了下来,为什么要赋值为负值呢?当然有道理。请听分解:
如下代码:
int find(int x){
return parent[x]<0 ? x : find(parent[x]);
}
我在搜索父亲的时候用碰到负值停下,这样就解决了搜索父亲的问题,只要是负值就是到了始祖节点,并且绝对之后就变成这棵树的节点数。
初始化前面讲过了直接将父亲都负值为-1,含义很明显绝对值之后是1说明这科树有1个节点。
今天在网上看了一篇博客,发现原来这种方法的并操作叫做启发式合并,就是尽量让深度少树连到深度大的树上,这样把树的深度变浅,相对的树的宽度变大。宽度变大不影响,深度变小节省了一大推搜索的时间。
网上的路径压缩和我的有很大差别,不过经过实验网上的和我的路径压缩都能解决题目,处理的时候也大同小异。
以下是网上的路径压缩:
搜先是搜索:
int find(intx)
{
if(parent[x]==x)
returnx;
returnparent[x]= find(parent[x]);
}
很明显的dfs+dp。每次搜索的结果都负值给这个数组。
这里分析一下:(如下图)
然后就是并的操作:(相对上面的来说就是小菜了)
void merge(inta,intb)
{
intfa=find(a);
intfb=find(b);
if(fa==fb)
return;
parent[fb]=parent[fa];
}
基本的操作结束了。
基础的题可以看acm集训mcs_7.23的倒数第三和倒数第四
接下来要讲的是在路径压缩的基础上操作一些基于父亲和种类的操作。
对于find()的变形比较大。
如下:
int find(intx)
{
if(parent[x]==x)
returnx;
intf=parent[x];
parent[x]=find(parent[x]);
操作。。。。。。。。。。
returnparent[x];
}
这里和之前不一样不是查到父亲就存下来,而是找个变量中转,因为接下来还要对父亲等进行操作,避免了因为父亲节点变了而引起操作错误。
并操作基本没变,只是在最后面添加处理额外操作的语句。
将了这么多要以实战来让自己更明白。
Poj 1182食物链
这个的并差集和以往不同,这种是对种类的并差集。
要新建一个数组存储路劲到根的距离然后模运算。具体看这里http://blog.csdn.net/ditian1027/article/details/20804911
这篇博客写的挺好的,敢说史上最为详细的解说也是有道理的。
附加题目 poj 1702
另外一种类型的题目,暂时不知道怎么分类。
直接看题吧 poj1988
很神奇的操作,感觉有个叫金海峰的人的博客讲的不错http://www.cnblogs.com/rainydays/archive/2011/03/13/1982677.html
(题解都在txt里面)