这俩东西听起来很高端,实际上很好写,应用也很多~

线段树合并

线段树合并,顾名思义,就是建立一棵新的线段树保存原有的两颗线段树的信息。

考虑如何合并,对于一个结点,如果两颗线段树都有此位置的结点,则直接合并两结点的信息(如维护最大值则取max,维护和则相加),然后递归处理左右子树;

若只有一个有,直接返回即可。

这样子做时间复杂度取决于重合节点个数,一次最坏复杂度是$O(nlogn)$,因为满二叉树的结点数是$O(n)$,对每个结点进行处理是$O(logn)$,但是实际应用中需要合并的两颗树重合部分一般较少,所以复杂度可以近似看为$O(logn)$的;

如果用动态开点线段树的话,一次合并只需要合并一条链,所以时间复杂度是$O(操作数\times logn)$的

启发式合并

启发式合并核心思想就一句话:把小集合的合并到大的里。

启发式合并思想可以放到很多数据结构里,链表、线段树、甚至平衡树都可以。

考虑时间复杂度,设总共有$n$个元素,由于每次集合的大小至少翻倍,所以至多会合并$logn$次,总的复杂度就是$O(nlogn)$的(结合线段树合并就是$O(nlog^2n)$的)

下面举几道例题:

【BZOJ1483】【HNOI2009】梦幻布丁

链表+启发式合并,每次换颜色直接合并

 #include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
int n,m,s,x,y,ans=,a[],lsh[],head[],nxt[],sum[],pre[];
void work(int x,int y){
for(int tmp=head[x];tmp!=-;tmp=nxt[tmp]){
if(a[tmp+]==y)ans--;
if(a[tmp-]==y)ans--;
}
for(int tmp=head[x];tmp!=-;tmp=nxt[tmp])a[tmp]=y;
nxt[lsh[x]]=head[y];
head[y]=head[x];
sum[y]+=sum[x];
head[x]=-;
lsh[x]=sum[x]=;
}
int main(){
memset(sum,,sizeof(sum));
memset(pre,,sizeof(pre));
memset(head,-,sizeof(head));
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++){
scanf("%d",&a[i]);
pre[a[i]]=a[i];
if(a[i]!=a[i-])ans++;
if(head[a[i]]==-)lsh[a[i]]=i;
sum[a[i]]++;
nxt[i]=head[a[i]];
head[a[i]]=i;
}
for(int i=;i<=m;i++){
scanf("%d",&s);
if(s==)printf("%d\n",ans);
else{
scanf("%d%d",&x,&y);
if(x==y)continue;
if(sum[pre[x]]>sum[pre[y]])swap(pre[x],pre[y]);
x=pre[x],y=pre[y];
if(!sum[x])continue;
work(x,y);
}
}
return ;
}

【BZOJ3123】【SDOI2013】森林

主席树+启发式合并

 #include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define N 1000000000
using namespace std;
typedef long long ll;
struct edge{
int v,next;
}a[];
int DYZ_HAS_CHANCE,n,m,t,u,v,w,ans=,tot=,cnt=,rt[],ls[],rs[],siz[],sum[],f[],num[],fa[][],dep[],head[];
bool used[];
char s[];
int ff(int u){
return f[u]==u?u:f[u]=ff(f[u]);
}
void add(int u,int v){
a[++tot].v=v;
a[tot].next=head[u];
head[u]=tot;
}
int lca(int u,int v){
if(dep[u]<dep[v])swap(u,v);
int l=dep[u]-dep[v];
for(int i=;i>=;i--){
if((<<i)&l)u=fa[u][i];
}
if(u==v)return u;
for(int i=;i>=;i--){
if(fa[u][i]!=fa[v][i]){
u=fa[u][i],v=fa[v][i];
}
}
return fa[u][];
}
void updata(int k,int &now,int l,int r,int v){
if(!now)now=++cnt;
siz[now]=siz[k]+;
if(l==r)return;
int mid=(l+r)/;
if(v<=mid)rs[now]=rs[k],updata(ls[k],ls[now],l,mid,v);
else ls[now]=ls[k],updata(rs[k],rs[now],mid+,r,v);
}
int query(int a1,int a2,int a3,int a4,int l,int r,int v){
if(l==r)return l;
int mid=(l+r)/,ret=siz[ls[a1]]+siz[ls[a2]]-siz[ls[a3]]-siz[ls[a4]];
if(v<=ret)return query(ls[a1],ls[a2],ls[a3],ls[a4],l,mid,v);
else return query(rs[a1],rs[a2],rs[a3],rs[a4],mid+,r,v-ret);
}
void dfs(int u,int f){
used[u]=true;
dep[u]=dep[f]+;
fa[u][]=f;
for(int i=;i<=;i++)fa[u][i]=fa[fa[u][i-]][i-];
updata(rt[f],rt[u],,N,num[u]);
for(int tmp=head[u];tmp!=-;tmp=a[tmp].next){
int v=a[tmp].v;
if(v!=f)dfs(v,u);
}
}
void merge(int u,int v){
int u1=ff(u),v1=ff(v);
f[u1]=v1;
sum[v1]+=sum[u1];
}
int main(){
memset(head,-,sizeof(head));
memset(used,,sizeof(used));
memset(rt,,sizeof(rt));
scanf("%d",&DYZ_HAS_CHANCE);
scanf("%d%d%d",&n,&m,&t);
for(int i=;i<=n;i++){
scanf("%d",&num[i]);
f[i]=i;
sum[i]=;
}
for(int i=;i<=m;i++){
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
merge(u,v);
}
for(int i=;i<=n;i++){
if(!used[i])dfs(i,);
}
for(int i=;i<=t;i++){
scanf("%s",s);
if(s[]=='Q'){
scanf("%d%d%d",&u,&v,&w);
u^=ans;
v^=ans;
w^=ans;
int now=lca(u,v);
printf("%d\n",ans=query(rt[u],rt[v],rt[now],rt[fa[now][]],,N,w));
}else{
scanf("%d%d",&u,&v);
u^=ans;
v^=ans;
int u1=ff(u),v1=ff(v);
if(sum[u1]>sum[v1])swap(u,v);
add(u,v);
add(v,u);
merge(u,v);
dfs(u,v);
}
}
return ;
}

【BZOJ3545】【ONTAK2010】Peaks

离线,按照困难度从小到大加边,用线段树维护每个联通块,每次合并联通块即可

 #include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
struct task{
int a,b,c,id,ok;
}a[];
struct treenode{
int v,ls,rs;
}t[];
int n,m,q,tot=,num[],_num[],fa[],rts[],ans[];
bool cmp(task a,task b){
return a.c==b.c?a.ok<b.ok:a.c<b.c;
}
int ff(int u){
return u==fa[u]?u:fa[u]=ff(fa[u]);
}
void updata(int &u,int l,int r,int v){
if(!u)u=++tot;
t[u].v=;
if(l==r)return;
int mid=(l+r)/;
if(v<=mid)updata(t[u].ls,l,mid,v);
else updata(t[u].rs,mid+,r,v);
}
int query(int u,int l,int r,int p){
if(l==r)return l;
int mid=(l+r)/;
if(p<=t[t[u].ls].v)return query(t[u].ls,l,mid,p);
else return query(t[u].rs,mid+,r,p-t[t[u].ls].v);
}
int merge(int u,int v){
if(!u||!v)return u|v;
if(!t[u].ls&&!t[u].rs){
t[u].v+=t[v].v;
return u;
}
t[u].ls=merge(t[u].ls,t[v].ls);
t[u].rs=merge(t[u].rs,t[v].rs);
t[u].v=t[t[u].ls].v+t[t[u].rs].v;
return u;
}
int main(){
scanf("%d%d%d",&n,&m,&q);
for(int i=;i<=n;i++){
scanf("%d",&num[i]);
_num[i]=num[i];
fa[i]=i;
}
sort(_num+,_num+n+);
for(int i=;i<=n;i++){
num[i]=lower_bound(_num+,_num+n+,num[i])-_num;
}
for(int i=;i<=m;i++){
scanf("%d%d%d",&a[i].a,&a[i].b,&a[i].c);
a[i].ok=;
}
for(int i=m+;i<=m+q;i++){
scanf("%d%d%d",&a[i].a,&a[i].c,&a[i].b);
a[i].ok=;
a[i].id=i-m;
}
sort(a+,a+m+q+,cmp);
for(int i=;i<=n;i++)updata(rts[i],,n,num[i]);
for(int i=;i<=m+q;i++){
if(a[i].ok==){
int u=ff(a[i].a),v=ff(a[i].b);
if(u!=v){
fa[u]=v;
rts[v]=merge(rts[u],rts[v]);
}
}else{
int u=ff(a[i].a);
if(t[rts[u]].v<a[i].b)ans[a[i].id]=-;
else ans[a[i].id]=_num[query(rts[u],,n,t[rts[u]].v-a[i].b+)];
}
}
for(int i=;i<=q;i++)printf("%d\n",ans[i]);
return ;
}

【BZOJ2212】【POI2011】Tree Rotation

直接从下往上线段树合并即可

 #include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;
struct treenode{
ll v;
int ls,rs;
}t[];
int n,tot=,cnt=,num[],ls[],rs[],rts[];
ll ans=,ans1,ans2;
void read(int u){
scanf("%d",&num[u]);
if(!num[u]){
read(ls[u]=++cnt);
read(rs[u]=++cnt);
}
}
void updata(int &u,int l,int r,int v){
if(!u)u=++tot;
if(l==r){
t[u].v=;
return;
}
int mid=(l+r)/;
if(v<=mid)updata(t[u].ls,l,mid,v);
else updata(t[u].rs,mid+,r,v);
t[u].v=t[t[u].ls].v+t[t[u].rs].v;
}
int merge(int u,int v){
if(!u||!v)return u|v;
ans1+=(ll)t[t[u].rs].v*t[t[v].ls].v;
ans2+=(ll)t[t[u].ls].v*t[t[v].rs].v;
t[u].ls=merge(t[u].ls,t[v].ls);
t[u].rs=merge(t[u].rs,t[v].rs);
t[u].v=t[t[u].ls].v+t[t[u].rs].v;
return u;
}
void dfs(int u){
if(!u)return;
dfs(ls[u]);
dfs(rs[u]);
if(!num[u]){
ans1=ans2=;
rts[u]=merge(rts[ls[u]],rts[rs[u]]);
ans+=min(ans1,ans2);
}
}
int main(){
scanf("%d",&n);
read();
for(int i=;i<=cnt;i++){
if(num[i])updata(rts[i],,n,num[i]);
}
dfs();
printf("%lld",ans);
return ;
}

现在搞专题沉迷摸鱼,一天平均只有两道题,颓废力max

线段树合并&&启发式合并笔记的更多相关文章

  1. 【BZOJ2733】永无乡(线段树,启发式合并)

    题意:支持合并,求块内K小数 对于 100%的数据 n≤100000,m≤n,q≤300000 思路:对于每一个块建立一棵动态开点的线段树,暴力(启发式?)合并后二分下就行了 merge用函数的方式写 ...

  2. [BZOJ4552][TJOI2016&&HEOI2016]排序(二分答案+线段树/线段树分裂与合并)

    解法一:二分答案+线段树 首先我们知道,对于一个01序列排序,用线段树维护的话可以做到单次排序复杂度仅为log级别. 这道题只有一个询问,所以离线没有意义,而一个询问让我们很自然的想到二分答案.先二分 ...

  3. Problem E. TeaTree - HDU - 6430 (树的启发式合并)

    题意 有一棵树,每个节点有一个权值. 任何两个不同的节点都会把他们权值的\(gcd\)告诉他们的\(LCA\)节点.问每个节点被告诉的最大的数. 题解 第一次接触到树的启发式合并. 用一个set维护每 ...

  4. 线段树:CDOJ1592-An easy problem B (线段树的区间合并)

    An easy problem B Time Limit: 2000/1000MS (Java/Others) Memory Limit: 65535/65535KB (Java/Others) Pr ...

  5. 线段树的区间合并 B - LCIS

    B - LCIS HDU - 3308 这个是一个很简单很明显的线段树的区间合并,不过区间合并的题目都还是有点难写,建议存个板子. #include <cstdio> #include & ...

  6. BZOJ.3673/3674.可持久化并查集(可持久化线段树 按秩合并/启发式合并)

    BZOJ 3673 BZOJ 3674(加强版) 如果每次操作最多只修改一个点的fa[],那么我们可以借助可持久化线段树来O(logn)做到.如果不考虑找fa[]的过程,时空复杂度都是O(logn). ...

  7. BZOJ.4919.[Lydsy1706月赛]大根堆(线段树合并/启发式合并)

    题目链接 考虑树退化为链的情况,就是求一个最长(严格)上升子序列. 对于树,不同子树间是互不影响的.仿照序列上的LIS,对每个点x维护一个状态集合,即合并其子节点后的集合,然后用val[x]替换掉第一 ...

  8. luogu P5161 WD与数列 SAM 线段树合并 启发式合并

    LINK:WD与数列 这道题可谓妙绝 我明白了一个增量统计的原理. 原本的想法是:差分之后 显然长度为1的单独统计 长度为2的以及更多就是字符串之间的匹配问题了. 对差分序列建立SAM 由于第一个是一 ...

  9. 【BZOJ3123】森林(主席树,启发式合并)

    题意:一个带点权的森林,要求维护以下操作: 1.询问路径上的点权K大值 2.两点之间连边 n,m<=80000 思路:如果树的结构不发生变化只需要维护DFS序 现在因为树的结构发生变化,要将两棵 ...

随机推荐

  1. Java入门基础—面向对象开发

    Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此Java语言具有功能强大和简单易用两个特征.Java语言作为静态面向对象编程语言的代表 ...

  2. 《Exception》第八次团队作业:Alpha冲刺(第二天)

    一.项目基本介绍 项目 内容 这个作业属于哪个课程 任课教师博客主页链接 这个作业的要求在哪里 作业链接地址 团队名称 Exception 作业学习目标 1.掌握软件测试基础技术.2.学习迭代式增量软 ...

  3. HDU 2078 选课时间( 水题 )

    链接:传送门 思路:水题略 /************************************************************************* > File N ...

  4. java实现登录的验证码和猜数字游戏_图形化界面

    实验任务四 1,出现设计思想 (1)先定义文本框.密码框和验证码框的组件 (2)定义面板和按钮的个数 (3)定义公有的虚构方法,通过对象实例化来调用 (4)利用Random类来实现生成0-9的随机数 ...

  5. django-4-模板标签,模板继承

    <<<模板标签>>> {% for %}{% endfor %} 循环 {% if %}{% elif %}{% else %}{% endif %} 判断 {% ...

  6. [LeetCode] 860. 柠檬水找零 lemonade-change(贪心算法)

    思路: 收到5块时,只是添加:收到十块时,添加10块,删除一个5块:收到20块时,添加20,删除一个10块一个5块,或者直接删除3个5块(注意:这里先删除5+10优于3个5) class Soluti ...

  7. 小学生都能学会的python(生成器)

    小学生都能学会的python(生成器) 1. 生成器 生成器的本质就是迭代器. 生成器由生成器函数来创建或者通过生成器表达式来创建 # def func(): # lst = [] # for i i ...

  8. iis解析json

    一. windows XP 1. MIME设置:在IIS的站点属性的HTTP头设置里,选MIME 映射中点击”文件类型”-”新类型”,添加一个文件类型:关联扩展名:*.json内容类型(MIME):a ...

  9. volley源代码解析(六)--HurlStack与HttpClientStack之争

    Volley中网络载入有两种方式,各自是HurlStack与HttpClientStack.我们来看Volley.java中的一段代码 if (stack == null) {//假设没有限定stac ...

  10. Swift的构造和析构过程

    构造过程 Swift的构造过程通过定义构造器来实现. 只是与Objective-C不同的是,Swift的构造器不须要返回值,相同也不须要表明Func. 另外值得提的是,当构造器中为存储型属性赋值时.不 ...