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

线段树合并

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

考虑如何合并,对于一个结点,如果两颗线段树都有此位置的结点,则直接合并两结点的信息(如维护最大值则取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. dedecms 添加自定义图字段,调用时出错

    1:发现问题 按照以往一样高兴的认为网站就绪了,谁知道出现一个小插曲. 使用dedecms 的站长们应该知道,当一个模型的字段的不能够满足我们项目的需求的时候就会自己创建一些字段,哈今天我就遇到了耶, ...

  2. 第一次接触Arduino

    1.百度百科: Arduino包含两个主要的部分:硬件部分是可以用来做电路连接的Arduino电路板:另外一个则是 Arduino IDE,你的计算机中的程序开发环境.你只要在IDE中编写程序代码,将 ...

  3. HDU 1205 吃糖果(水题)

    链接:传送门 思路:思维僵硬了,僵硬...... 简单的插隔板思想......选出来数量最多的糖果种类X,假设X数量为MAX,然后以X作为"隔板",形成X _ X _ X _ X ...

  4. HDU 2079 选课时间(母函数模板题)

    链接:传送门 思路:母函数模板题 /************************************************************************* > Fil ...

  5. Python字典嵌套

    import copy menu = { "计算机科学与技术":{ "程序编程":{ "传统语言":{ "C++":&q ...

  6. Ajax原理-重点

    Ajax原理 AJAX即“Asynchronous Javascript And XML”,是指一种创建交互式网页应用的网页开发技术.AJAX 是一种用于创建快速动态网页的技术.它可以令开发者只向服务 ...

  7. tp框架表单提交注意!不要提交到当前方法

    tp框架  表单提交到当前方法,会重复执行显示部分和保存部分的代码.导致不知名的错误.

  8. 机器学习关于AUC的理解整理

    AUC 几何意义:ROC曲线与X轴的面积 https://blog.csdn.net/luo3300612/article/details/80367901 AUC物理意义:随机给定一个正样本和一个负 ...

  9. 2015 Multi-University Training Contest 8 hdu 5384 Danganronpa

    Danganronpa Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)Tot ...

  10. PatentTips - Increasing turbo mode residency of a processor

    BACKGROUND Many modern operating systems (OS's) use the Advanced Configuration and Power Interface ( ...