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

线段树合并

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

考虑如何合并,对于一个结点,如果两颗线段树都有此位置的结点,则直接合并两结点的信息(如维护最大值则取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. 动态生成的dom元素绑定事件

    要求:要绑定到父元素上$(".school_Inlists").on("click",".chose_Inbtn",function(){ ...

  2. Java基础之Colloction

    0 引言 以下是介绍Java有关集合类,以及对应每个类的用途,同时进行比较集合类的不同特点来让我们深入了解. 1 Collction接口 Collection是最基本的集合接口,一个Collectio ...

  3. stylus 移动端边框1像素问题解决方案

    border($border-width = 1px, $border-color = #ccc, $border-style = solid, $radius = 0) // 为边框位置提供定位参考 ...

  4. PHP下的Oauth2.0尝试 - OpenID Connect

    OpenID Connect OpenID Connect简介 OpenID Connect是基于OAuth 2.0规范族的可互操作的身份验证协议.它使用简单的REST / JSON消息流来实现,和之 ...

  5. crm 系统项目(一) 登录,注册,校验

    crm 系统项目(一) 登录,注册,校验 首先创建一个Django项目,关于配置信息不多说,前面有~ models.py文件下创建需要的表格信息,之后导入数据库 from django.db impo ...

  6. Shiro:初识Shiro及简单尝试

    Shiro 一.什么是Shiro Apache Shiro是Java的一个安全(权限)框架 作用:认证.授权.加密.会话管理.与web集成.缓存等 下载地址:http://shiro.apache.o ...

  7. 【Codeforces Round #482 (Div. 2) C】Kuro and Walking Route

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 把x..y这条路径上的点标记一下. 然后从x开始dfs,要求不能走到那些标记过的点上.记录节点个数为cnt1(包括x) 然后从y开始 ...

  8. 数字签名技术与https

    1,非对称加密技术 非对称加密算法需要两个密钥,公开密钥(publickey)和私有密钥(privatekey):公钥和私钥是成对出现的. 非对称加密例子:B想把一段信息传给A,步骤:1)A把公钥传给 ...

  9. HTML5与后台服务器的数据流动问题

    编辑中,尚未完稿...2017.7.14 1345 很多前端开发出来的HTML5可能对于后台开发者来说,并不是很清楚,也许像我一样一知半解.而且真的让人很糊涂的地方就是前端的JS如何与后端的数据库进行 ...

  10. ZOJ 3690 Choosing number(dp矩阵优化)

    Choosing number Time Limit: 2 Seconds      Memory Limit: 65536 KB There are n people standing in a r ...