线段树(不要看,我自己都看不太懂)

距离蓝桥杯只有不到四周,不知道学这个还有没有用,理解模板都费劲了,估计也不能举一反三吧。。。

//先看概念,就是在整个区间上建立二叉树
//设 区间为 a[N] , 线段树数组为 tree[N]
//则从顶部递归建立二叉树

inline void build(int i,int l,int r)
{
    if(l==r)//左右边界相等则tree[ i ]==a[ i ]
    {
        tree[i]=a[l];
        return;
    }
    //对左右两边分别建树
    int mid=(l+r)>>1;
    build(i*2,l,mid),build(i*2+1,mid+1,r);
    //建完之后根据左右两边的结果更新当前 l ~ r 的结果
    push(i);
}

//可以看到还需要一个 push 函数进行左右两边建树后的更新
inline void push(int i)
{
    //根据需要,看是找什么,可能是最大值、累加、次大值。。。。
    //不同的情况的 push 不同
    tree[i]=max(tree[i*2],tree[i*2+1]);
}

//懒惰标签,即每次找到管辖区域在区间内的,就直接更新节点,并进行标记,下次有需要再查到他的子节点的话就在根据标签往下更新后再查询
inline void spread(int i)
{
    if(tag[i])
    {
        tree[i*2]+=tag[i];
        tree[i*2+1]+=tag[i];
        tag[i]=0;
    }
}

//建完树了,我们肯定是要用来查什么东西,就要有 ask 函数
//这里仅以最大值距离,还是那句话,情况不同,ask 也不同
inline int ask(int i,int s,int t,int l,int r)
{
    if(l<=s&&t<=r)
    {
        return tree[i];
    }
    spread(int i,int s,int t);
    if(r<s||t<l) return 0;//处理越界
    int mid=(s+t)>>1;//求mid用的是 i 的管辖区间 s , t 不是查询的区间 l , r !!!!
    return max(ask(i*2,s,mid,l,r),ask(i*2+1,mid+1,r,l,r));
}

//建完树之后,可能还会有修改的需求,根据 对 a[x] 的修改 ,修改 tree[]
//和建树差不多,只是建树是每次dfs到叶子节点就更新对应的 tree[i]
//而修改则是dfs到需要修改的 a[x] 进行更新,对两边dfs完后也是用原来的push求当前区间的tree[i],大差不差
//同样,进行修改要看具体要求的是什么值
void modify(int i,int l,int r,int x,int val)
{
    if(x<l||r>x) return;
    if(l==x&&r==x)
    {
        tree[i]=a[x]=val;
        return;
    }
    //如果使用懒惰标签
    //if(l<=x&&x<=r)
    //{
    //  tree[i]+=val;
    //}
    //spread(int i,int l,int r);
    int mid=(l+r)>>1;
    modify(i*2,l,mid,x,val),modify(i*2+1,mid+1,r,x,val);
    push(i);
}

例题:
封印宝石
看题解抄了三天才勉强理解一点
首先,找的宝石一定是 res[i] 的后面的宝石
最大化字典序就注定了要贪心地找 i 之后的最大魔力值的宝石
但是还有个点,每次都要扣除 根据 id 来计算的体力,所以只存魔力值不行,还要存 id
所以用结构体数组 E( id ,ener )

struct E{
	int id,ene;
	E(){id=ene=0;}
}zero;

同时为了减少体力消耗,同 ener 的情况下,视 id 小的为大

bool operator <(E a,E b)
{
	if(a.ene==b.ene) return a.id>b.id;
	return a.ene<b.ene;
}

bool operator ==(E a,E b)
{
	return a.ene==b.ene;
}

用线段树维护最大 E

但是有个问题,相邻的 res[i] 不能放相同魔力值的宝石,能直接跳过吗?
不能,既然当前最大的魔力值不能放,那就放当前的次大值
也就是说,线段是要求同时维护最大 E 和次大 E
怎么做到呢?
那线段树也开 结构体!
同时存 辖区内的 最大 E 和次大 E

struct Tree
{
	int l,r;
	E max1,max2;
	Tree(){max1.ene=max1.id=0,max2.ene=max2.id=0;};
}tree[4*N];
E al[4];

那么就思考 各个函数该怎么变化
首先是push,我们要对比两个 儿子 tree 来找出最大和次大,再更新到 tree[i]

inline void push(int i)
{
	al[0]=tree[i*2].max1,al[1]=tree[i*2].max2;
	al[2]=tree[i*2+1].max1,al[3]=tree[i*2+1].max2;
	sort(al,al+4);
	tree[i].max1=al[3];
	for(int j=2;j>=0;j--)
	{
		if(al[j].ene!=al[j+1].ene)
		{
			tree[i].max2=al[j];
			break;
		}
	}
}

然后是 build
build 的逻辑就是dfs到每个叶子节点,更新对应的 tree 后return
对之后根据两半的结果更新父节点
所以只要改 跟新tree的操作

inline void build(int i,int l,int r)
{
	if(l==r)
	{
		tree[i].max1.ene=a[l],tree[i].max1.id=l,tree[i].l=l, tree[i].r=r;
		return;
	}
	int mid=(l+r)>>1;
	build(i*2,l,mid);
	build(i*2+1,mid+1,r);
	push(i);
}

然后是 ask
ask 的逻辑就是:我们要查 l ~ r 的数据,
而线段树把区间分成了 2^k 的分段,而任何数都能用 2 进制表示
所以就是从 根节点 1 开始往下dfs,找到在辖区 l ~ r 内的tree [] 的就把这个 tree[] 的数据return 回去

但是现在要找的是最大值和次大值,两个值,一个return无法满足需求
同时,对于查询区间内的所有子段来说,就算return 能返回 最大和次大,也还需要找出总的最大和次大,这不是max函数能做到的,所以要另找两个变量储存当前的 最大和次大,每次找到区间内的子段就找到这两个变量和这个字段的 最大和次大 中的最大和次大,并更新 这两个变量

E m1,m2;
inline E ask(int i,int s,int t,int l,int r)
{
	if(l<=s&&t<=r)
	{
		al[0]=tree[i].max1,al[1]=tree[i].max2;
		al[2]=m1,al[3]=m2;
		sort(al,al+4);
		m1=al[3];
		for(int j=2;j>=0;j--)
		{
			if(al[j].ene!=al[j+1].ene)
			{
				m2=al[j];
				break;
			}
		}
		return tree[i].max1;
	}
	if(l>t||r<s) return zero;
	int mid=(s+t)>>1;
	return max(ask(i*2,s,mid,l,r),ask(i*2+1,mid+1,t,l,r));
}

最后是修改函数 modify
我们唯一要做的修改就是把已经拿过的宝石变为0,这样找最大值时就不会找上他,所以不需要更改原数据,只要该 tree 就行

inline void modify(int i,int l,int r,int x)
{
	if(x<l||x>r) return;
	if(l==x&&r==x)
	{
		tree[i].max1=tree[i].max2=zero;
		return;
	}
	int mid=(l+r)>>1;
	modify(i*2,l,mid,x),modify(i*2+1,mid+1,r,x);
	push(i);
}

搞了半天发现最上面的有些狗屁不通,自己都看不懂,虽然本来就是用来给自己看的,下面这个更全面些

#include<bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int N=1e5+10;
LL n,mod,t[4*N],a[N],sum,q;
struct Tag{
	LL time,plu;
	Tag():time(1),plu(0){};
	Tag(LL t,LL p):time(t),plu(p){};
}tag[4*N];

inline void push(LL i)
{
	t[i]=(t[i*2]+t[i*2+1])%mod;
}

inline void spread(LL i,LL S,LL T)
{
	if(tag[i].time!=1||tag[i].plu!=0)
	{
		int mid=(S+T)>>1;
		tag[i*2].time=(tag[i*2].time*tag[i].time)%mod;
		tag[i*2].plu=(tag[i*2].plu*tag[i].time+tag[i].plu)%mod;
		t[i*2]=(t[i*2]*tag[i].time+tag[i].plu*(mid-S+1))%mod;
		
		tag[i*2+1].time=(tag[i*2+1].time*tag[i].time)%mod;
		tag[i*2+1].plu=(tag[i*2+1].plu*tag[i].time+tag[i].plu)%mod;
		t[i*2+1]=(t[i*2+1]*tag[i].time+tag[i].plu*(T-mid))%mod;
		
		tag[i]=Tag(1,0);
	}
}

inline void build(LL i,LL l,LL r)
{
	if(l==r)
	{
		t[i]=a[l]%mod;
		return;
	}
	LL mid=(l+r)>>1;
	build(i*2,l,mid);
	build(i*2+1,mid+1,r);
	push(i);
}

inline void modify_t(LL i,LL S,LL T,LL x,LL y,LL k)
{
	if(y<S||x>T) return;
	if(x<=S&&T<=y)
	{
		t[i]=(t[i]*k)%mod;
		tag[i].time=(tag[i].time*k)%mod;
		tag[i].plu=(tag[i].plu*k)%mod;
		return;
	}
	spread(i,S,T);
	LL mid=(S+T)>>1;
	modify_t(i*2,S,mid,x,y,k);
	modify_t(i*2+1,mid+1,T,x,y,k);
	push(i);
	return;
}

inline void modify_p(LL i,LL S,LL T,LL x,LL y,LL k)
{
	if(y<S||x>T) return;
	if(x<=S&&T<=y)
	{
		t[i]=(t[i]+(T-S+1)*k)%mod;
		tag[i].plu=(tag[i].plu+k)%mod;
		return;
	}
	spread(i,S,T);
	LL mid=(S+T)>>1;
	modify_p(i*2,S,mid,x,y,k);
	modify_p(i*2+1,mid+1,T,x,y,k);
	push(i);
	return;
}

inline LL ask(LL i,LL S,LL T,LL x,LL y)
{
	if(y<S||x>T) return 0;
	if(x<=S&&T<=y)
	{
		return t[i];
	}
	spread(i,S,T);
	LL mid=(S+T)>>1;
	return (ask(i*2,S,mid,x,y)%mod+ask(i*2+1,mid+1,T,x,y)%mod)%mod;
}

int main() {
	cin>>n>>q>>mod;
	for(LL i=1;i<=n;i++) scanf("%lld",&a[i]);
	build(1,1,n);
	for(LL i=1;i<=q;i++)
	{
		LL o,x,y,k;
		scanf("%lld",&o);
		if(o==1)
		{
			scanf("%lld%lld%lld",&x,&y,&k);
			modify_t(1,1,n,x,y,k);
		}
		else if(o==2)
		{
			scanf("%lld%lld%lld",&x,&y,&k);
			modify_p(1,1,n,x,y,k);
		}
		else if(o==3)
		{
			scanf("%lld%lld",&x,&y);
			sum=ask(1,1,n,x,y)%mod;
			cout<<sum<<'\n';
		}
	}

    return 0;
}

posted @ 2025-03-21 00:25  石磨豆浆  阅读(21)  评论(0)    收藏  举报