7.30集训——线段树

目录

李超线段树

动态开点线段树

 可持久化线段树(主席树)

启发式合并


luogu P3372线段树模板

#include<bits/stdc++.h>
#define MAXN 1000001
#define ll long long
using namespace std;
unsigned ll n,m,a[MAXN],ans[MAXN<<2],tag[MAXN<<2];
inline ll ls(ll x)
{
    return x<<1;
}
inline ll rs(ll x)
{
    return x<<1|1;
}
void scan()
{
    cin>>n>>m;
    for(ll i=1;i<=n;i++)
    {
    	scanf("%lld",&a[i]);
	}
}
inline void push_up(ll p)
{
    ans[p]=ans[ls(p)]+ans[rs(p)];
}
void build(ll p,ll l,ll r)
{
    tag[p]=0;
    if(l==r)
	{
		ans[p]=a[l];
		return ;
	}
    ll mid=(l+r)>>1;
    build(ls(p),l,mid);
    build(rs(p),mid+1,r);
    push_up(p);
} 
inline void f(ll p,ll l,ll r,ll k)
{
    tag[p]=tag[p]+k;
    ans[p]=ans[p]+k*(r-l+1);
}
inline void push_down(ll p,ll l,ll r)
{
    ll mid=(l+r)>>1;
    f(ls(p),l,mid,tag[p]);
    f(rs(p),mid+1,r,tag[p]);
    tag[p]=0;
}
inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
{
    if(nl<=l&&r<=nr)
    {
        ans[p]+=k*(r-l+1);
        tag[p]+=k;
        return ;
    }
    push_down(p,l,r);
    ll mid=(l+r)>>1;
    if(nl<=mid)
	{
		update(nl,nr,l,mid,ls(p),k);
	}
    if(nr>mid) 
	{
		update(nl,nr,mid+1,r,rs(p),k);
	}
    push_up(p);
}
ll query(ll q_x,ll q_y,ll l,ll r,ll p)
{
    ll res=0;
    if(q_x<=l&&r<=q_y)
	{
		return ans[p];
	}
    ll mid=(l+r)>>1;
    push_down(p,l,r);
    if(q_x<=mid)
	{
		res+=query(q_x,q_y,l,mid,ls(p));
	}
    if(q_y>mid) 
	{
		res+=query(q_x,q_y,mid+1,r,rs(p));	
	}
    return res;
}
int main()
{
    ll a1,b,c,d,e,f;
    scan();
    build(1,1,n);
    while(m--)
    {
        scanf("%lld",&a1);
        switch(a1)
        {
            case 1:
            {
                scanf("%lld%lld%lld",&b,&c,&d);
                update(b,c,1,n,1,d);
                break;
            }
            case 2:
            {
                scanf("%lld%lld",&e,&f);
                printf("%lld\n",query(e,f,1,n,1));
                break;
            }
        }
    }
    return 0;
}

洛⾕ P3801 红⾊的幻想乡

n*m(n, m <= 105)的矩形区域中,进⾏如下两个操作: 1 x y : 蕾⽶莉亚站在坐标(x,y)的位置向四个⽅向释放⽆限的 红雾,当两个红雾相遇在同⼀点,则会沉降消失。 2 x1 y1 x2 y2 : 询问左上点为(x1,y1),右下点为(x2,y2)的矩 形范围内,被红雾遮盖的地区的数量

  • ⼀块n*m⼦矩阵中,施加 过红雾的⾏数为x、列数为y,则相遇抵消的点数 为x*y, 则红雾覆盖的点 数为 x*m+y*n-2*x*y

洛⾕ P2824 [HEOI2016/TJOI2016]排序

给出⼀个1到n的全排列,现在对这个全排列序列进⾏m次局部排序, 排序分为两种:
1:(0,l,r)表示将区间[l,r]的数字升序排序;
2:(1,l,r)表示将区间[l,r]的数字降序排序。
最后询问第q位置上的数字。 n, m \leqslant 10^5

  • 二分q点的答案ans,原序列中小于等于ans的看成0,大于ans的看成1,这样区间排序就变成区间求和然后区间修改了

洛⾕ P4198 楼房重建

小 A 的楼房外有一大片施工工地,工地上有 N 栋待建的楼房。每天,这片工地上的房子拆了又建、建了又拆。他经常无聊地看着窗外发呆,数自己能够看到多少栋房子。

为了简化问题,我们考虑这些事件发生在一个二维平面上。小 A 在平面上 (0,0)(0,0)(0,0) 点的位置,第 iii 栋楼房可以用一条连接 (i,0) 和 (i,H_{i}) 的线段表示,其中 H_{i} 为第 i 栋楼房的高度。如果这栋楼房上任何一个高度大于 0 的点与 (0,0) 的连线没有与之前的线段相交,那么这栋楼房就被认为是可见的。

施工队的建造总共进行了 M 天。初始时,所有楼房都还没有开始建造,它们的高度均为 0。在第 i 天,建筑队将会将横坐标为 X_{i} 的房屋的高度变为 Y_{i}(高度可以比原来大—修建,也可以比原来小—拆除,甚至可以保持不变—建筑队这天什么事也没做)。请你帮小 A 数数每天在建筑队完工之后,他能看到多少栋楼房?

  • 首先,把所有的高度a[i]除以坐标i,得到从(0,0)望过去的直线斜率,判断楼与楼是否遮挡直接比较斜率大小即可
  • ans[k]表示节点k区间中的“单调递增序列”度(从左到右能看到的楼的数量)
  • 考虑由左右儿子的ans求出父亲节点的ans
  • 左儿子的ans都可以对父亲节点的ans做出贡献,而右儿子的ans则可能已经被左儿子中的楼遮挡,设左儿子的最大斜率为h,若能用calc(k,h)求出节点k区间中大于h的楼的“单调递增序列”度,则ans[k]=ans[k<<<<1]
  • 那么如何写这个calc(k,h)函数呢?还是分情况讨论
  1. 若左子树中没有大于h的,则返回calc(k<<1|1,h)
  2. 若左子树中有大于h的,则calc(k<<1|1,h)全部对calc(k,h)有贡献,右子树对ans[k]贡献的答案中都是大于h的楼房,则这个答案(ans[k]-ans[k<<1])全部对calc(k,h)有贡献,返回的就是calc(k<<1,h)+ans[k]-ans[k<<1]
  • 这样调用一次calc()函数复杂度是O(\log n),总共调用O(\log n)次,总复杂度O(n\log 2n)

李超线段树

 用于解决区间添加一次函数、单点查询所有一次函数最大值的问题

洛⾕ P4097 [HEOI2013]Segment
要求在平⾯直⻆坐标系下维护两个操作:

  1. 在平⾯上加⼊⼀条线段。记第 i 条被插⼊的线段的标号为 i
  2. 给定⼀个数 k, 询问与直线 x = k 相交的线段中,交点最靠上的线段的编号。
  • 维护一个线段树,线段树每个节点维护对应区间的“优势线段”,即区间中点对应值最大的线段
  • 当在一个节点上添加一条线段时,分情况讨论:
  1. 当新线段完全被原优势线段覆盖时,新线段没用,不做修改
  2. 当新线段完全覆盖原优势线段时,原优势线段没用,将整个子树优势线段改为新线段
  3. 当两者有交点时,新优势线段为两者中中点对应值较大的一条,另一条递归添加到交点所在的一侧儿子中
  • 写着写着可以发现——这个标记可以永久化
  • 标记永久化:标记不进行下传,查询某一点时,取根到该叶子路径上所有标记的最优值

动态开点线段树

我们来观察一下普通线段树的左儿子和右儿子的表示方法

左儿子:p<<1

右儿子:p<<1|1

这样,虽然我们可以直接算出左右儿子,比较方便,但是,这样也浪费了大量的空间

链式储存法:即对一个节点维护其左右儿子的编号,指向它的左右儿子,这样,只有当访问某个节点时,我们才需要建立该节点,减小了空间

//单点修改
int insert(int k,int l,int r,int x)
{
	if(!k)
	{
		k=++tot;
	}
	if(l==r)
	{
		return sum[k]=a[l],k;
	}	
	if(x<=mid)
	{
		ls[k]=insert(ls[k],l,mid,x);
	}
	else
	{
		re[k]=insert(re[k],mid+1,r,x);
	}
	sum[k]=sum[ls[k]]+sum[rs[k]];
	return k;
} 
//区间查询
int query(int k,int l,int r,int x,int y)
{
	if(x<=l&&r<=y)
	{
		return sum[k];		
	}
	int mid=(l+r)>>1,res=0;
	if(x<=mid)
	{
		res+=query(ls[k],l,mid,x,y);
	}
	if(y>mid)
	{
		res+=query(rs[k],mid+1,r,x,y);
	}
	return res;
} 

 可持久化线段树(主席树)

对于一个经过m次修改操作的线段树,可持久化线段树能维护其所有的m个历史版本

我们发现,每次修改只会修改线段树中的\log n个节点,因此我们可以只对这\log n个点开新节点,其余沿用旧树上的点

//单点修改
void update(int &o,int l,int r,int last,int p,int v)
{
	o=++tot;
	ls[o]=ls[last];
	rs[o]=rs[last];
	if(l==r)
	{
		a[o]=v;
		return ;	
	}	
	int m=(l+r)>>1;
	if(p<=m)
	{
		update(ls[o],l,m,ls[last],p,v);
	}
	else
	{
		update(rs[o],m+1,r,rs[last],p,v);
	}
} 

 查询无区别

静态区间第k小
给定n个数的序列,m次查询区间[l,r]的第k小值,n,m<=2e5

权值数组:一个数组a满足a[i]值为i的数的个数

权值线段树:维护权值数组的线段树

假如我们获得了[l,r]的权值线段树,查询时若左儿子区间和>=k则递归左儿子,若<k则递归右儿子即可

如何求[l,r]的权值线段树

可持久化线段树维护T_{i}=[1,i]的权值线段树

T_{r}-T_{l}-1即为[l,r]的权值线段树

//查询
int query(int s,int t,int l,int r,int x)
{
	if(l==r)
	{
		return l;
	}
	int cnt=sum[ls[t]]-sum[ls[s]];
	if(cnt>=x)
	{
		return query(ls[s],ls[t],l,mid,x);
	}	
	else
	{
		return query(rs[s],rs[t],mid+1,r,x-cnt);
	}
} 

将两棵动态开点线段树合并到一起(对应位置相加)

如果一方对应位置没有点的话就用另一方的,都有就递归下去

复杂度O(\min(n_{1},n_{2}))

//合并
int merge(int p1,int p2,int l,int r)//表示将树p2的信息合并到树p1上 
{
	if(!p1||!p2)
	{
		return p1+p2;	
	} 
	if(l==r)
	{
		s[p1]=s[p1]+s[p2];
		return p1;
	}
	int mid=(l+r)>>1;
	ls[p1]=merge(ls[pl],ls[p2],l,mid);
	rs[p1]=merge(rs[p1],rs[p2],mid+1,r);
	s[p1]=s[ls[pl]]+s[rs[pl]];
	return p1;
} 

启发式合并

定义:将n个集合\begin{Bmatrix} a_{1} \end{Bmatrix}\begin{Bmatrix} a_{2} \end{Bmatrix}...\begin{Bmatrix} a_{n} \end{Bmatrix},合并集合A,B时,将较小的集合中的元素一个个插入到较大集合中,单次复杂度为\min(size(A),size(B))

复杂度:O(n\log n)

证明:每个数在跟比它所在集合更大的集合合并时会产生1的贡献,所在集合大小至少翻一倍,最多产生\log n次贡献

黑科技

用于解决一些插入容易实现而删除难以实现的问题

用一个\log的代价将问题转化成只有插入

线段树分治

一个n个节点的图,m次操作,每次插入一条边,删除一条边。Q次查询,每次查询图是不是二分图。n,m<=1e5

  • 线段树分治实际上是一种维护时间区间的数据结构,同样是利用线段树的分治性,让复杂度保证在了\log级别
  • 开一棵维护操作时间的线段树,我们考虑如何维护一个操作影响的时间区间,假设一个操作影响的时间时[L,R],将其呈现在线段树上找这个询问所在时间点,把根到叶子路径上的所有影响计算一遍,就能得出这个询问的答案
  • 每次递归整棵线段树,到达一个节点时执行该节点上的所有操作,离开时撤销

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值