首页 > 其他分享 >线段树+动态开点权值线段树+主席树学习笔记

线段树+动态开点权值线段树+主席树学习笔记

时间:2023-08-25 23:00:52浏览次数:48  
标签:int 线段 权值 seg ans query 开点 ll

线段树一般用于维护符合结合律的信息。可以用于求区间最大值 区间和 区间最小值 最大子段和甚至于最大负数最小正数之类的信息。事实上线段树只有你想不到,很少有做不到的,算是相当常用的数据结构。
下面将结合个人理解和具体题目来讲一讲线段树。

[https://www.luogu.com.cn/problem/P8818](csps2022 策略游戏)

仔细分析,这是个非常烦的例题,不仅要判断中间是否有零,还要判断最小正数和最大正数,线段树也可以维护这些如此繁琐的信息。

https://www.luogu.com.cn/blog/novax13/game-solution

#include <cstdio>
#define max(a,b) (((a)>(b))?(a):(b))
#define min(a,b) (((a)<(b))?(a):(b))
#define ls(p) ((p)<<1)
#define rs(p) (((p)<<1)|1)
const int Nx=100010;
int N,M,Q,A[Nx],B[Nx];
struct node{long long maz,miz,maf,mif,zro;};
node As[4*Nx],Bs[4*Nx];
void pushnode(node &a,int val)
{
    if(val==0)
        a.zro=1;
    else if(val>0)
        a.maz=a.miz=val;
    else if(val<0)
        a.maf=a.mif=val;
}
node merge(node x,node y)
{
    node ret;
    ret.zro=x.zro||y.zro;
    if(!x.maz||!y.maz)
    {
        ret.maz=x.maz+y.maz;
        ret.miz=x.miz+y.miz;
    }
    else
    {
        ret.maz=max(x.maz,y.maz);
        ret.miz=min(x.miz,y.miz);
    }
    if(!x.maf||!y.maf)
    {
        ret.maf=x.maf+y.maf;
        ret.mif=x.mif+y.mif;
    }
    else
    {
        ret.maf=max(x.maf,y.maf);
        ret.mif=min(x.mif,y.mif);
    }
    return ret;
}
void build(int ll,int rr,int p,node seg[],int C[])
{
    if(ll==rr)
    {
        pushnode(seg[p],C[ll]);
        return;
    }
    int mid=(ll+rr)>>1;
    build(ll,mid,ls(p),seg,C);
    build(mid+1,rr,rs(p),seg,C);
    seg[p]=merge(seg[ls(p)],seg[rs(p)]);
}
node query(int ll,int rr,int p,int L,int R,node seg[])
{
    if(L<=ll&&rr<=R)
        return seg[p];
    int mid=(ll+rr)>>1;
    if(L>mid)
        return query(mid+1,rr,rs(p),L,R,seg);
    if(R<=mid)
        return query(ll,mid,ls(p),L,R,seg);
    return merge(query(ll,mid,ls(p),L,R,seg),query(mid+1,rr,rs(p),L,R,seg));
}
long long get_ans(node ax,node bx)
{
    long long ret;
    //printf("%d %d %d %d %d   %d %d %d %d %d\n",ax.maz,ax.miz,ax.maf,ax.mif,ax.zro,bx.maz,bx.miz,bx.maf,bx.mif,bx.zro);
    if(bx.mif!=0&&bx.miz==0)//后手无正数
    {
        if(ax.mif!=0)//先手有负数
            ret=(bx.zro)?0:ax.mif*bx.maf;
        else//先手无负数
            ret=(ax.zro)?0:ax.miz*bx.mif;
    }
    else if(bx.mif==0&&bx.miz!=0)//后手无负数
    {
        if(ax.miz!=0)//先手有正数
            ret=(bx.zro)?0:ax.maz*bx.miz;
        else//先手无正数
            ret=(ax.zro)?0:ax.maf*bx.maz;
    }
    else//后手正负都有/后手只有0
    {
        if(ax.zro)
            ret=0;
        else if(ax.mif!=0&&ax.miz==0)//先手无正数
            ret=ax.maf*bx.maz;
        else if(ax.mif==0&&ax.miz!=0)//先手无负数
            ret=ax.miz*bx.mif;
        else//先手正负都有
            ret=max(ax.miz*bx.mif,ax.maf*bx.maz);
    }
    return ret;
}
int main()
{
    scanf("%d%d%d",&N,&M,&Q);
    int i,j,k,la,ra,lb,rb;
    for(i=1;i<=N;i++)
        scanf("%d",&A[i]);
    for(i=1;i<=M;i++)
        scanf("%d",&B[i]);
    build(1,N,1,As,A);
    build(1,M,1,Bs,B);
    node ax,bx;
    while(Q--)
    {
        scanf("%d%d%d%d",&la,&ra,&lb,&rb);
        ax=query(1,N,1,la,ra,As);
        bx=query(1,M,1,lb,rb,Bs);
        printf("%lld\n",get_ans(ax,bx));
    }
}

这是普通线段树,接下来讲一种功能更加强大的线段树,动态开点线段树。

[https://www.luogu.com.cn/problem/P3960](NOIP2017 列队)

题目要求我们建立一个数据结构,支持将一个数放到尾部,以及查询当前第k个数。并做到删除。

直接建树时间会很爆炸,不如我们维护一个01序列,0表示当前没有,1表示当前有。如此建立一个动态开点线段树。

1-n/1-m的数字很好维护,我们开个vector记录后面添加的数即可。
https://www.luogu.com.cn/blog/Peterprpr/solution-p3960

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ri register int
const int N=3e5+5;

ll num[N<<1] ,rt[N],n,m,q;
ll cnt=1;
ll ls[N<<5],rs[N<<5],sum[N<<5];
int query(ll &p,ll l,ll r,ll x){
	if(!p) p=++cnt,sum[p]=r-l+1;
	sum[p]--;
	if(l==r){
		return l;
	}
	ll mid=l+r>>1,k;
	if(!ls[p]) k=mid-l+1;
	else k=sum[ls[p]];
	if(x<=k) return query(ls[p],l,mid,x);
	else return query(rs[p],mid+1,r,x-k);
}
vector<ll> g[N];
int main(){
	std::ios::sync_with_stdio(false);
	cin>>n>>m>>q;
	ll len=max(n,m)+q;
	for(ri i=1;i<=n;i++) num[i]=i*m;
	for(ri i=1;i<=q;i++){
		ll x,y;
		cin>>x>>y;
		if(y==m){
			ll ans=query(rt[n+1],1,len,x);
			num[i+n]=num[ans];
		}
		else{
			ll ans=query(rt[n+1],1,len,x);
			g[x].push_back(num[ans]);
			ans=query(rt[x],1,len,y);
			if(ans>=m) num[i+n]=g[x][ans-m];
			else num[i+n]=ans+m*(x-1);
		}
		cout<<num[i+n]<<endl;
	}
}

上面的话是用动态开点线段树+vector维护一段序列,那么接下来应该是最厉害的应用。
动态开点权值线段树
首先是求逆序对。和树状数组一样,不表。
其次是这道题。

https://www.luogu.com.cn/problem/P5459

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ri register int
const ll N=1e5+5;

ll len=N*N;

ll a[N],n,L,R,cnt=1,sum[N<<6];
int ls[N<<6],rs[N<<6],rt;

void insert(int&p,ll l,ll r,ll x){
	if(!p) p=++cnt;
	sum[p]++;
	if(l==r) return ;
	ll mid=l+r>>1;
	if(x<=mid) insert(ls[p],l,mid,x);
	else insert(rs[p],mid+1,r,x);
}

ll query(int&p,ll l,ll r,ll nl,ll nr){
	if(!p) p=++cnt;//以后询问时也可以动态开点记住了
	if(nl<=l&&r<=nr){
		return sum[p];
	}
	ll ans=0,mid=l+r>>1;
	if(nl<=mid) ans+=query(ls[p],l,mid,nl,nr);
	if(nr>mid) ans+=query(rs[p],mid+1,r,nl,nr);
	return ans;
}

int main(){
	std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>n>>L>>R;
	for(ri i=1;i<=n;i++) cin>>a[i],a[i]+=a[i-1];
	ll res=0;
	for(ri i=1;i<=n;i++){
		insert(rt,-len,len,a[i-1]);
		ll l=a[i]-R,r=a[i]-L;
		res+=query(rt,-len,len,l,r);
		//cout<<query(rt,-len,len,l,r)<<endl;
	}
	cout<<res;
}

主席树

又称为可持久化权值线段树。发明人黄嘉泰原话:对于原序列的每一个前缀[1···i]建立出一棵线段树维护值域上每个数出现的次数,则其树是可减的。

例题以后再补。

标签:int,线段,权值,seg,ans,query,开点,ll
From: https://www.cnblogs.com/Kopicy/p/17658131.html

相关文章

  • 【主席树】洛谷 P3834 可持久化线段树 2
    【主席树】洛谷P3834可持久化线段树2题目链接:https://www.luogu.com.cn/problem/P3834主席树是可持久化线段树的一种,也叫做可持久化权值线段树,主要可以用来O(logn)求静态区间的第k小数。总所周知,普通线段树每次修改会遍历logn个点,那么我们在每次修改时都把这logn个点复制一份......
  • 可持久化线段树标记永久化?可刺激化修道士表舅已经黑!
    关于可刺激化修道士表舅已经黑。因为傻逼lxd告诉我我的表舅已经黑写法是错误的,所以稀里糊涂的让他改成了他的那种写法。但是我的也是对的。比如区间加和区间查和,维护一个\(tag\),表示表舅的值。然后在区间加的时候,经过的区间的\(sum\)的值可以直接加,但是只有在if(x<=l&......
  • 线段树
    线段树vs树状数组代码长度:树状数组段可扩展性:线段树强,二树状数组仅局限于和的处理思维难度:线段树简单比如区查区改树状数组还要打开多项式搞延迟标记:为了处理当修改区间是\([1,n]\)时所有节点都要被修改一遍的情况如果修改区间覆盖当前区间,那么这颗子树之内所有节点......
  • 【230823-3】▲ABC中,∠ABC=90°,AB=4,BC=3,点D在线段AC上,若角BDC=45°,则BD=?,Cos∠ABD=?
    ......
  • 数学——点到线段的最短距离
    点到线段最短距离的运算与点到直线的最短距离的运算二者之间存在一定的差别,即求点到线段最短距离时需要考虑参考点在沿线段方向的投影点是否在线段上,若在线段上才可采用点到直线距离公式。通俗的说,我们按照求点到直线的距离作垂线后,交点不一定在线段上。如图\(1\)所示。通常......
  • [Trick] [算法学习笔记] 线段树
    事先声明:本文并非线段树教学。只是一些理解Trick。若您需从0学起线段树建议您移步其他博文呢qwq感谢Idea提供尺子姐姐的博客!,尺子好闪,拜谢尺子!我们在学习线段树的时候,对于乘法“lazytag先乘再加”是不是难以理解?这里介绍一种线段树思考方法。我们可以将序列中的每个元素......
  • 线段树进阶
    多信息合并\(\text{GSS3Solution}\)\(\text{link}\)对于线段树的每个结点,记录其区间和(\(sum\)),区间前后缀最大子段和(\(lmax,rmax\))和区间最大子段和(\(vmax\))。合并:\[vmax=\max\{vmax_{lson},vmax_{rson},rmax_{lson}+lmax_{rson}\}\]\[lmax=\max\{lmax_{lson},sum_{lson}+lm......
  • 【学习笔记】优化建图相关(线段树优化,倍增优化)
    优化建图发现并没有人写得很详细的样子,那我也摆烂好惹点击查看目录目录前言线段树优化建图单点连区间区间连区间例题解题:倍增优化建图例题解题:前言众所周知,连边的时间复杂度一般是\(O(1)\),但,当连边的对象是一个连续的树上区间的时候,我们或许有更优的连边方式:优化建图。......
  • 大抄线段树历史值问题
    历史值问题历史值:在维护序列\(A\)的同时,在每次操作后,序列\(A\)会对序列\(B\)的对应位置产生贡献。历史版本和:每次操作后,\(B_i\leftarrowB_i+A_i\)。历史最大值:每次操作后,\(B_i=\max(B_i,A_i)\)。历史版本和:给定操作:①区间加。②查询区间和。③查询区间......
  • 【230820-1】▲ABC中,AC=根号二,BC=根号六,S△ABC=根号三/2,若线段BA上的延长线存在点D,使
    ......