时间过得很快,这段日子已经走完了一半,照例做一个今天的小结和知识整理。
今天讲了一些算法,数量不多,难度却是有的,所以接下来的每个算法都是详细讲述的。
frist.并查集
所谓的并查集就是一种类似于小组的东西,被称为集合,且不同的集合可以进行合并。可用于处理合并集合和查找集合的算法,即将数据作为集合储存,有一个代表元作为这个集合的总的父亲节点,同时,这个点也是自己的父亲节点。
作为并查集,肯定要具备并,查,集,等特点,下面将其一一实现
1.“集”就是这个算法是用来处理集合问题的;我们可以先将每个数都各自看作是一个集合,由此来实现初始化,代码如下:
for(int i = 1 ; i <= n ; ++i)
father[i] = i;
2.“并”就是可以按需要将属于同一组的元素所在的集合合并;其实只是将其代表元做了改变,代码如下:
int findFather(int x){
if(father[x] == x)return x;
else return findFather(father[x]);
}
3.“查”就是查询元素所在集合,查询不同元素、不同集合之间的关系。实现代码如下:
int fu , fv;
fu = findFather(u);
fv = findFather(v);
father[fu] = fv;
second.树状数组
树状数组用于频繁的求和操作,而且速度快,易于改变数据量。它是利用跳跃存储来实现的,跳跃大小定义为
lowbit(x)(即x的二进制表示,仅保留最低位1得到的数字)
若用代码实现可写为:
int lowbit(x){
return (x & (-x));
}
要实现求前缀和,还要快,就是利用这个跳跃,然后完成树状数组,代码实现如下:
int getsum(int k){
int sum = 0;
while(k > 0)
{
sum += star[k];
k -= lowbit(k);
}return sum;
}
然后就是动态修改,修改时只需把增值同步到位置上即可,如代码所示:
void modify(int x , int delta){
while(x <= N){
star[x] += delta;
x += lowbit(x);
}
}
这样便实现了树状数组;
third.线段树
常用于求区间特征值,可以快速完成查找和修改;
1.建树,有树才能用,想解题,先建树,可通过递归建树,代码如下:
void build(int id , int l , int r)
{
tree[id].left=l; tree[id].right=r;
if (l==r){
tree[id].sum=a[l];
tree[id].max=a[l];
}
else{
int mid=(l+r)/2;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
tree[id].max=max(tree[id*2].max,tree[id*2+1].max;
}
}
2.查找,完成了建树就要进行利用,我们可以通过区间查找去寻找区间特征值,用代码实现如下:
3.单个数据的修改,为了实现频繁的查找和改变,我们可以利用回溯对数据进行更新,代码如下:
int query(int id , int l , int r){
if (tree[id].left==l&&tree[id].right==r)
return tree[id].sum; //询问总和,可改为最值
}
else{
int mid=(tree[id].left+tree[id].right)/2;
if (r<=mid) return query(id*2,l,r);
else
if (l>mid) return query(id*2+1,l,r)
else
return query(id*2,l,mid)+query(id*2+1,mid+1,r);
}
}
void update(int id , int pos , int val){
if (tree[id].left==tree[id].right){
tree[id].sum=tree[id].max=val;
}
else{
int mid=(tree[id].left+tree[id].right)/2;
if (pos<=mid) update(id*2,pos,val);
else update(id*2+1,pos,val);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
tree[id].max=max(tree[id*2].max,tree[id*2+1].max)
}
}
4.区间的数据更新,因为效率问题,我们肯定不能用上面的代码重复多次来实现,因此,我们要给tree增加一个
标记,并对查找做出改变,来一段代码深入理解一下:
void pushdown(int id){
if(tree[id].tag == true){
tree[id*2].tag = tree[id*2 + 1].tag = true; //下传标记到左右儿子
tree[id*2].delta += tree[id].delta; //下传变动记录到左儿子,以便左儿子继续下传
tree[id*2].max += tree[id].delta; //更改左儿子数值
tree[id*2].sum += (tree[id*2].r – tree[id*2].l + 1) * tree[id].delta;
tree[id*2 + 1].delta += tree[id].delta; //下传变动记录到右儿子,以便右儿子继续下传
tree[id*2 + 1].max += tree[id].delta; //更改右儿子数值
tree[id*2 + 1].sum += (tree[id*2 + 1].r – tree[id*2 + 1].l + 1) * tree[id].delta;
tree[id].tag = false; //儿子已被修改,清空标记
tree[id].delta = 0;
}
}
void query(int id , int l , int r){
if (tree[id].left==l&&tree[id].right==r){
return tree[id].sum;
}
else{
pushdown(id); // 不得不访问儿子了,就要下传
int mid=(tree[id].left+tree[id].right)/2;
if (r<=mid) return query(id*2,l,r);
else
if (l>mid) return query(id*2+1,l,r)
else
return query(id*2,l,mid) +
query(id*2+1,mid+1,r);
}
}
void modify(int id , int l , int r , int x){
if (tree[id].left>=l && tree[id].right<=r)
tree[id].max+=x;
tree[id].sum+=x*(tree[id].r-tree[id].l+1);// 标记儿子需要修改但尚未修改
tree[id].tag = true; tree[id].delta = x;
· }
else{
pushdown(id); // 不得不访问儿子了,就要下 传
int mid=(tree[id].left+tree[id].right)/2;
if (r<=mid) modify(id*2,l,r);else
if (l>mid) modify(id*2+1,l,r)
else{
modify(id*2,l,mid);
modify(id*2+1,mid+1,r);
}
tree[id].sum= tree[id*2].sum+tree[id*2+1].sum;
tree[id].max=max(tree[id*2].max,tree[id*2+1].max);
}
}
这样就完成了线段树的操作;
END.