线段树合并小结

一种新的线段树 pushup 方法:

friend node operator + (const node &xx,const node &yy)
{
node z;
z.cnt=xx.cnt+yy.cnt;
z.sum=xx.sum+yy.sum;
return z;
}

然后改的时候就直接使用

tr[now]=tr[lid]+tr[rid];

权值线段树

就是把线段树变成桶。

用线段树维护桶。

代码:

模板:P1138 第 k 小整数

#include<bits/stdc++.h>
using namespace std;
int n,k;
struct segmentTree{
struct node{
int sum;
}tr[40000<<2];
#define lid now<<1
#define rid now<<1|1
void update(int now,int l,int r,int x,int y,int k)
{
if(x<=l&&r<=y)
{
tr[now].sum=k;return ;
}
int mid=(l+r)>>1;
if(x<=mid) update(lid,l,mid,x,y,k);
if(y>mid) update(rid,mid+1,r,x,y,k);
tr[now].sum=tr[lid].sum+tr[rid].sum;
}
int query(int now,int l,int r,int k)
{
if(l==r) return l;
int mid=(l+r)>>1;
if(tr[lid].sum>=k) query(lid,l,mid,k);
else query(rid,mid+1,r,k-tr[lid].sum);
}
}st;
int cnt;
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
int t;cin>>t;
st.update(1,1,30000,t,t,1);
// cout<<"upd\n";
}
if(k<=0||k>=st.tr[1].sum)
{
cout<<"NO RESULT";return 0;
}
cout<<st.query(1,1,30000,k);
}

作用

  1. 查询第 k 大的数。
int kth(int now,int l,int r,int k)
{
if(l==r) return l;
int mid=(l+r)>>1;
if(tr[lid].sum>=k) return kth(lid,l,mid,k);
else return kth(rid,mid+1,r,k-tr[lid].sum);
}
  1. 算逆序对(就是模板粘上去即可)

动态开点树

可以是线段树,也可以是权值线段树。

代码:

模板: P3369 【模板】普通平衡树

  • 要注意的一点是,update 的时候的 now 要写成 int &now 来更改值;

  • 每个操作 update 或者 query 的范围可以是负数、传进去的参数也可以是负数。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
int root=1,cnt=1;
int mx=2e7+1;
struct sgt{
struct node{
int sum,ls,rs;
}tr[2000000<<1];
#define lid tr[now].ls
#define rid tr[now].rs
void update(int &now,int l,int r,int x,int y,int k)
{
if(!now) now=++cnt;
if(x<=l&&r<=y)
{
tr[now].sum+=k;
return ;
}
int mid=(l+r)>>1;
if(x<=mid) update(lid,l,mid,x,y,k);
if(y>mid) update(rid,mid+1,r,x,y,k);
tr[now].sum=tr[lid].sum+tr[rid].sum;
}
int query(int now,int l,int r,int x,int y)
{
if(x<=l&&r<=y) return tr[now].sum;
int mid=(l+r)>>1,res=0;
if(x<=mid) res+=query(lid,l,mid,x,y);
if(y>mid) res+=query(rid,mid+1,r,x,y);
return res;
}
int kth(int now,int l,int r,int k)
{
if(l==r) return l;
int mid=(l+r)>>1;
if(tr[lid].sum>=k) return kth(lid,l,mid,k);
else return kth(rid,mid+1,r,k-tr[lid].sum);
}
}st;
map<int,int>mp;
signed main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
int op,t;
scanf("%lld%lld",&op,&t);
if(op==1)
{
st.update(root,-mx,mx,t,t,1);
mp[t]++;
}
else if(op==2)
{
st.update(root,-mx,mx,t,t,-1);
mp[t]--;
if(!mp[t]) mp.erase(t);
}
else if(op==3)
{
cout<<st.query(root,-mx,mx,-mx,t-1)+1<<endl;
}
else if(op==4)
{
int res=st.kth(root,-mx,mx,t);
cout<<res<<endl;
}
else if(op==5)
{
cout<<(--mp.lower_bound(t))->first<<endl;
}
else if(op==6)
{
cout<<mp.upper_bound(t)->first<<endl;
}
}
}

作用

缩小使用的空间。

例题

有的题目多解,可以直接使用权值线段树也可以使用线段树合并。

例题1 P3369 【模板】普通平衡树

看似是平衡树模板,实则是动态开点权值线段树模板。

只不过查找前驱后继的操作我拿 map 水过了。。

代码在上面。

例题2 #P1644. bzoj4636: 韶身的数列

这是拿动态开点权值线段树做的。

但是里面有一个操作很迷,就是修改比 k 小的数为 k

void ckmx(int &x,int y)
{
x=(y>x?y:x);
}
void update(int &now,int l,int r,int x,int y,int k)
{
if(!now) now=++cnt;
if(x<=l&&r<=y)
{
ckmx(tr[now].mx,k);//这里
return ;
}
int mid=(l+r)>>1;
if(x<=mid) update(lid,l,mid,x,y,k);
if(y>mid) update(rid,mid+1,r,x,y,k);
}

还有就是查询的神秘操作:

void query(int now,int l,int r,int pre)
{
if(!now&&!pre) return ;
int mid=(l+r)>>1;
int nnow=max(pre,tr[now].mx);//这里
if(l==r) ans+=nnow;
else query(lid,l,mid,nnow),query(rid,mid+1,r,nnow);
}

例题3 P3605 [USACO17JAN] Promotion Counting P

这个题很好,我拿动态开点权值线段树做的。

题意就是求树上逆序对,整体方法是 dfs

对于一个答案 ans[u]

ans[u]= 加上子树中比它大的个数 - 原本线段树中比它大的。

这样我们每 dfs 到一个节点 u,先在 ans[u] 中减去比 u 大的个数,然后 dfs 子节点,之后再给 ans[u] 加上线段树中比它大的。

最后,把当前节点的权值 update 进去。

void dfs(int u,int fa)
{
ans[u]-=st.query(root,1,mx,ww[u]+1,mx);
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa) continue;
dfs(v,u);
}
ans[u]+=st.query(root,1,mx,ww[u]+1,mx);
st.update(root,1,mx,ww[u],ww[u],1);
}

然后有个细节:我们有可能 query 的节点是空的,所以在 query 函数中需要加上一行 if(!now) return 0;

具体来说是这样的:

int query(int now,int l,int r,int x,int y)
{
if(!now) return 0;
if(x<=l&&r<=y) return tr[now].sum;
int mid=(l+r)>>1,res=0;
if(x<=mid) res+=query(lid,l,mid,x,y);
if(y>mid) res+=query(rid,mid+1,r,x,y);
return res;
}

例题4 P4556 [Vani有约会] 雨天的尾巴 /【模板】线段树合并

线段树合并 + 树链剖分

每个节点都是一个(动态开点)权值线段树,

然后树上差分:

  • u 到根节点加上 1;
  • v 到根节点加上 1
  • lca 到根节点减去 1
  • fa[lca] 到根节点减去 1

然后再一遍 dfs 合并线段树,至此结束全部的神秘操作。

线段树结构体数组要开 30 倍

待完全理解。。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=3e5+1;
struct node{
int to,next;
}edge[N<<1];
int head[N],ccnt;
void add(int u,int v)
{
edge[++ccnt].next=head[u];
edge[ccnt].to=v;
head[u]=ccnt;
}
int ans[N];
int siz[N],son[N],dep[N],f[N];
void dfs1(int u,int fa)
{
siz[u]=1,dep[u]=dep[fa]+1;
f[u]=fa;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa) continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[son[u]]<siz[v]) son[u]=v;
}
}
int top[N],id[N],tim;
void dfs2(int u,int t)
{
id[u]=++tim;top[u]=t;
if(!son[u]) return;
dfs2(son[u],t);
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==f[u]||v==son[u]) continue;
dfs2(v,v);
}
}
int lca(int u,int v)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=f[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
return u;
}
int root=1,cnt=1;
struct SegTree{
struct nodee{
int ls,rs,sum,tre;
}tr[N*30];
#define lid tr[now].ls
#define rid tr[now].rs
void pushup(int now)
{
if(tr[lid].sum>=tr[rid].sum)
tr[now].sum=tr[lid].sum,tr[now].tre=tr[lid].tre;
else
tr[now].sum=tr[rid].sum,tr[now].tre=tr[rid].tre;
}
void change(int &now,int l,int r,int pos,int val)
{
if(!now) now=++cnt;
if(l==r)
{
tr[now].sum+=val;
tr[now].tre=pos;return;
}
int mid=(l+r)>>1;
if(pos<mid) change(lid,l,mid,pos,val);
else change(rid,mid+1,r,pos,val);
pushup(now);
}
int merge(int a,int b,int l,int r)
{
if(a==0||b==0) return a+b;
if(l==r)
{
tr[a].sum+=tr[b].sum;return a;
}
int mid=(l+r)>>1;
tr[a].ls=merge(tr[a].ls,tr[b].ls,l,mid);
tr[a].rs=merge(tr[a].rs,tr[b].rs,mid+1,r);
pushup(a);
return a;
}
int rot[N];
void calc(int u,int fa)
{
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa) continue;
calc(v,u);
rot[u]=merge(rot[u],rot[v],1,100000);
}
ans[u]=tr[rot[u]].tre;
if(tr[rot[u]].sum==0) ans[u]=0;
}
}st;
int main()
{
cin>>n>>m;
for(int i=1;i<n;i++)
{
int u,v;cin>>u>>v;
add(u,v),add(v,u);
}
dfs1(1,0),dfs2(1,1);
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
int lc=lca(u,v);
st.change(st.rot[u],1,100000,w,1);
st.change(st.rot[v],1,100000,w,1);
st.change(st.rot[lc],1,100000,w,-1);
st.change(st.rot[f[lc]],1,100000,w,-1);
}
st.calc(1,0);
for(int i=1;i<=n;i++)
cout<<ans[i]<<endl;
}

例题5 P3224 [HNOI2012] 永无乡

芝士模板。

但是又有玄学操作!

用并查集维护联通块。用动态开点权值线段树和线段树合并维护区间第 k 大。

因为查询第 k 大的代码参数写反了,一直跑不出来。。。。。

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int f[N];
int find(int x)
{
if(x!=f[x])
f[x]=find(f[x]);
return f[x];
}
int ccnt=1;int num[N];
struct SegTrMerge{
struct node{
int ls,rs,sum,id;
}tr[N*30];
#define lid tr[now].ls
#define rid tr[now].rs
void pushup(int now){tr[now].sum=tr[lid].sum+tr[rid].sum;}
void change(int &now,int l,int r,int x)
{
if(!now) now=++ccnt;
tr[now].sum++;
if(l==r)
return ;
int mid=(l+r)>>1;
if(x<=mid) change(lid,l,mid,x);
else change(rid,mid+1,r,x);
}
int query(int now,int l,int r,int k)
{
if(tr[now].sum<k||!now) return 0;
if(l==r) return num[l];
int mid=(l+r)>>1;
if(k<=tr[lid].sum) return query(lid,l,mid,k);
else return query(rid,mid+1,r,k-tr[lid].sum);
}
void merge(int &a,int b)
{
if(!a){a=b;return ;}
if(!b) return ;
tr[a].sum+=tr[b].sum;
merge(tr[a].ls,tr[b].ls);
merge(tr[a].rs,tr[b].rs);
}
}st;
int root[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;f[i]=i;num[x]=i;
st.change(root[i],1,n,x);
}
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
u=find(u),v=find(v);
f[v]=u;
st.merge(root[u],root[v]);
}
int q;cin>>q;
while(q--)
{
string c;cin>>c;
int u,v;
cin>>u>>v;
if(c[0]=='Q')
{
u=find(u);
if(st.tr[root[u]].sum<v)
{
cout<<-1<<endl;
continue;
}
cout<<st.query(root[u],1,n,v)<<endl;
}
else
{
u=find(u),v=find(v);
if(u==v) continue;
f[v]=u;
st.merge(root[u],root[v]);
}
}
}

例题6 P4219 [BJOI2014] 大融合

把它放到例题里面纯粹是因为在波波的训练里头。

我是参考洛谷的一篇树剖解子做的。

用树剖+并查集来想就会非常简单。

首先把操作离线,建一个操作里叙述的森林,

然后建一个虚拟根节点,把森林连成一棵树。(也可以直接使用 lct

跑树剖代码,把树状数组初始化一下,清空并查集。

然后对于一个操作:

  • 操作 A
    find(x)x 的路径上每个点都加上 query(y)
  • 操作 Q
    如果 xy 的父节点,那么答案就是 (query(x)query(y))(query(y))

query(x) 表示当前 x 的子树大小。

#include<bits/stdc++.h>
using namespace std;
int n,q;
const int N=1e5+2;
int f[N];
int find(int x)
{
if(x!=f[x]) f[x]=find(f[x]);
return f[x];
}
struct operation{
int tp,x,y;
}op[N];
struct node{
int to,next;
}edge[N<<1];
int head[N],cnt;
void add(int u,int v)
{
edge[++cnt].to=v;
edge[cnt].next=head[u];
head[u]=cnt;
}
void read()
{
cin>>n>>q;
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=q;i++)
{
getchar();
char c=getchar();
if(c=='A') op[i].tp=1;
else op[i].tp=0;
cin>>op[i].x>>op[i].y;
if(op[i].tp)
{
add(op[i].x,op[i].y),add(op[i].y,op[i].x);
f[find(op[i].y)]=find(op[i].x);
}
}
}
int tim,top[N],id[N],siz[N],son[N],dep[N],father[N];
void dfs1(int u)
{
siz[u]=1;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==father[u]) continue;
father[v]=u;
dep[v]=dep[u]+1;
dfs1(v);
siz[u]+=siz[v];
if(siz[son[u]]<siz[v]) son[u]=v;
}
}
void dfs2(int u,int t)
{
top[u]=t,id[u]=++tim;
if(!son[u]) return ;
dfs2(son[u],t);
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==father[u]||v==son[u]) continue;
dfs2(v,v);
}
}
int lowbit(int x){return x&-x;}
int c[N];
void update(int x,int k){
for(;x<=n;x+=lowbit(x))c[x]+=k;
}
int query(int x)
{
int res=0;
for(;x>0;x-=lowbit(x))
res+=c[x];
return res;
}
int addpath(int u,int v,int k)
{
while(top[u]!=top[v])
{
update(id[top[u]],k),update(id[u]+1,-k);
u=father[top[u]];
}
update(id[v],k),update(id[u]+1,-k);
}
int main()
{
read();
for(int i=1;i<=n;i++)
{
if(find(i)==i) add(i,n+1),add(n+1,i);//连森林成一棵树
}
n++;//玄学
dfs1(n),dfs2(n,n+1);
update(1,1);
for(int i=1;i<=n;i++) f[i]=i;//cout<<c[i]<<" ";
for(int i=1;i<=q;i++)
{
int x=op[i].x,y=op[i].y;
if(f[x]==y) swap(x,y);
if(op[i].tp==1)
{
addpath(x,find(x),query(id[y]));//cout<<"add\n";
f[find(y)]=find(x);
}
else
{
long long ans=0;
int s=query(id[find(x)]),s1=query(id[y]);
ans=(s-s1)*s1;
cout<<ans<<endl;
}
}
}
posted @   ccjjxx  阅读(36)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示