「Luogu P6242 【模板】线段树 3」

吉老师线段树模板题,如果想要知道更多可以去看看2016年的国集论文.

分析

先来看5个操作(第一感觉):

  1. 区间加,只要用懒标记搞一下就好了.
  2. 区间取min,吉老师线段树基本操作,记录一下最大值,最大值出现次数,严格次大值就好了.
  3. 区间和,直接维护就好了.
  4. 区间max,已经维护了.
  5. 区间历史max,维护一下最大加标记就好了.

然后您就可以得到30分的好成绩.

考虑一下这个做法有什么问题.

可以发现问题是出在标记合并上.

可以发现在取min时最大的数的加标记会发生改变,那么需要额外维护出最大的数的加标记和其他的数的加标记(最大加标记).

在标记下传时,如果修改的子树的最大值是两子树最大值的最大值那么它的最大值的修改和其他值的修改可能不同,否则是相同的.

关于吉老师线段树的复杂度证明建议去看看论文或者其他的博客(我太菜了).

具体还是看代码吧.

代码

#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
using namespace std;
const int MAXN=5e5+5;
const int MAX_NUM=5e5;
const long long INF=1e18;
long long maxll(long long a,long long b)
{
	if(a>b)
	{
		return a;
	}
	return b;
}
int n,m;
int arr[MAXN];
struct LazyTag
{
	long long max_add,add;//对于非最大数的最大加标记和当前加标记
	long long add_max_for_max,add_for_max;//对于最大数的最大加标记和当前加标记
	void Clean()//清空标记
	{
		add=0;
		add_for_max=0;
		max_add=0;
		add_max_for_max=0;
	}
}for_make;
LazyTag MakeTagAdd(int add)//对于加操作的修改中所有标记都是相同的
{
	for_make.Clean();
	for_make.add=add;
	for_make.max_add=add;
	for_make.add_for_max=add;
	for_make.add_max_for_max=add;
	return for_make;
}
LazyTag MakeTagMin(int min,int now_max)//对于取min操作,只有对最大值有修改
{
	for_make.Clean();
	for_make.add_for_max=min-now_max;
	for_make.add_max_for_max=min-now_max;
	return for_make;
}
struct SegmentTree
{
	long long first_max,second_max;
	long long max_num;
	long long history_max;
	long long sum;
	LazyTag tag;
}sgt[MAXN*4];
#define LSON (now<<1)
#define RSON (now<<1|1)
#define MIDDLE ((left+right)>>1)
#define LEFT LSON,left,MIDDLE
#define RIGHT RSON,MIDDLE+1,right
#define NOW now_left,now_right
void PushUp(int now)//合并子树
{
	sgt[now].history_max=maxll(sgt[LSON].history_max,sgt[RSON].history_max);//历史最大值直接取max
	sgt[now].sum=sgt[LSON].sum+sgt[RSON].sum;//区间和直接相加
	if(sgt[LSON].first_max==sgt[RSON].first_max)//对于最大值和严格次大值需要分类讨论
	{
        //如果两子树最大值相同
		sgt[now].first_max=sgt[LSON].first_max;//当前最大值就是子树最大值
		sgt[now].max_num=sgt[LSON].max_num+sgt[RSON].max_num;//最大值出现次数是子树出现次数之和
		sgt[now].second_max=maxll(sgt[LSON].second_max,sgt[RSON].second_max);//严格次大是两子树的严格次大中的大值
	}
	else
	{
		if(sgt[LSON].first_max>sgt[RSON].first_max)//如果最大值不同,那么就比较最大值
		{
			sgt[now].first_max=sgt[LSON].first_max;//最大值就是两子树的最大值
			sgt[now].max_num=sgt[LSON].max_num;//出现次数就是大的数的出现次数
			sgt[now].second_max=maxll(sgt[RSON].first_max,sgt[LSON].second_max);//严格次大值
		}
		else
		{
			sgt[now].first_max=sgt[RSON].first_max;
			sgt[now].max_num=sgt[RSON].max_num;
			sgt[now].second_max=maxll(sgt[LSON].first_max,sgt[RSON].second_max);
		}
	}
}
void Build(int now=1,int left=1,int right=n)//建树
{
	sgt[now].tag.Clean();
	if(left==right)
	{
		sgt[now].first_max=
		sgt[now].history_max=
		sgt[now].sum=
		arr[left];//直接等于初值
		sgt[now].second_max=-INF;//无严格次大
		sgt[now].max_num=1;//出现次数为1
		return;
	}
	Build(LEFT);
	Build(RIGHT);
	PushUp(now);
}
void DownMax(LazyTag tag,int now,int left,int right)//对于有最大值的下传标记
{
	sgt[now].sum+=
	1ll*tag.add_for_max*sgt[now].max_num
	+1ll*tag.add*(right-left+1-sgt[now].max_num);//计算贡献时需要分开计算
	sgt[now].tag.max_add=//对于非最大值的最大加标记的修改
	maxll(
		sgt[now].tag.max_add,
		sgt[now].tag.add+tag.max_add//和原来的加标记+下传的标记中的最大加标记的和取大值
	);
	sgt[now].tag.add_max_for_max=//对于最大值同理
	maxll(
		sgt[now].tag.add_max_for_max,
		sgt[now].tag.add_for_max+tag.add_max_for_max
	);
	sgt[now].history_max=//历史最大值也同理
	maxll(
		sgt[now].history_max,
		sgt[now].first_max+tag.add_max_for_max//注意是对于最大值的最大加标记
	);
	sgt[now].first_max+=tag.add_for_max;//加上的是对于最大值的加标记
	if(sgt[now].second_max!=INF)//如果存在严格次大值
	{
		sgt[now].second_max+=tag.add;//加上的是非最大值的加标记
	}
	sgt[now].tag.add+=tag.add;//对于加标记直接修改就可以
	sgt[now].tag.add_for_max+=tag.add_for_max;

}
void Down(LazyTag tag,int now,int left,int right)//没有最大值的标记下传
{
	tag.add_for_max=tag.add;//和上面的区别只有将最大值的修改变为非最大值的修改
	tag.add_max_for_max=tag.max_add;
    
	sgt[now].sum+=
	1ll*tag.add_for_max*sgt[now].max_num
	+1ll*tag.add*(right-left+1-sgt[now].max_num);
	sgt[now].tag.max_add=
	maxll(
		sgt[now].tag.max_add,
		sgt[now].tag.add+tag.max_add
	);
	sgt[now].tag.add_max_for_max=
	maxll(
		sgt[now].tag.add_max_for_max,
		sgt[now].tag.add_for_max+tag.add_max_for_max
	);
	sgt[now].history_max=
	maxll(
		sgt[now].history_max,
		sgt[now].first_max+tag.add_max_for_max
	);
	sgt[now].first_max+=tag.add_for_max;
	if(sgt[now].second_max!=INF)
	{
		sgt[now].second_max+=tag.add;
	}
	sgt[now].tag.add+=tag.add;
	sgt[now].tag.add_for_max+=tag.add_for_max;
}
void PushDown(int now,int left,int right)
{
	int max=maxll(sgt[LSON].first_max,sgt[RSON].first_max);//取出max
	if(max==sgt[LSON].first_max)//如果等于max,那么就是有最大值的标记下传,否则是没有最大值的下传
	{
		DownMax(sgt[now].tag,LEFT);
	}
	else
	{
		Down(sgt[now].tag,LEFT);
	}
	if(max==sgt[RSON].first_max)
	{
		DownMax(sgt[now].tag,RIGHT);
	}
	else
	{
		Down(sgt[now].tag,RIGHT);
	}
	sgt[now].tag.Clean();//清空标记
}
void UpdataAdd(int now_left,int now_right,int add,int now=1,int left=1,int right=n)//区间加的操作
{
	if(now_right<left||right<now_left)
	{
		return;
	}
	if(now_left<=left&&right<=now_right)//直接修改就可以
	{
		Down(MakeTagAdd(add),now,left,right);
		return;
	}
	PushDown(now,left,right);
	UpdataAdd(NOW,add,LEFT);
	UpdataAdd(NOW,add,RIGHT);
	PushUp(now);
}
void UpdataMin(int now_left,int now_right,int min,int now=1,int left=1,int right=n)//区间取min的操作
{
	if(now_right<left||right<now_left||sgt[now].first_max<=min/*适当剪枝*/)
	{
		return;
	}
	if(now_left<=left&&right<=now_right)
	{
		if(sgt[now].first_max>min&&min>sgt[now].second_max)//如果当前数是在最大值和严格次大值之间那么就打上标记修改
		{
			DownMax(MakeTagMin(min,sgt[now].first_max),now,left,right);
			return;
		}//否则继续递归,到叶节点一定是符合的
	}
	PushDown(now,left,right);
	UpdataMin(NOW,min,LEFT);
	UpdataMin(NOW,min,RIGHT);
	PushUp(now);
}
long long QuerySum(int now_left,int now_right,int now=1,int left=1,int right=n)//区间和,和普通线段树相同
{
	if(now_right<left||right<now_left)
	{
		return 0;
	}
	if(now_left<=left&&right<=now_right)
	{
		return sgt[now].sum;
	}
	PushDown(now,left,right);
	return QuerySum(NOW,LEFT)+QuerySum(NOW,RIGHT);
}
long long QueryMax(int now_left,int now_right,int now=1,int left=1,int right=n)//区间max
{
	if(now_right<left||right<now_left)
	{
		return -INF;
	}
	if(now_left<=left&&right<=now_right)
	{
		return sgt[now].first_max;
	}
	PushDown(now,left,right);
	return maxll(QueryMax(NOW,LEFT),QueryMax(NOW,RIGHT));
}
long long QueryHistoryMax(int now_left,int now_right,int now=1,int left=1,int right=n)//区间历史max
{
	if(now_right<left||right<now_left)
	{
		return -INF;
	}
	if(now_left<=left&&right<=now_right)
	{
		return sgt[now].history_max;
	}
	PushDown(now,left,right);
	return maxll(QueryHistoryMax(NOW,LEFT),QueryHistoryMax(NOW,RIGHT));
}
#undef LSON
#undef RSON
#undef MIDDLE
#undef LEFT
#undef RIGHT
#undef NOW
int main()
{
	scanf("%d%d",&n,&m);
	REP(i,1,n)
	{
		scanf("%d",&arr[i]);
	}
	Build();//建树
	int opt,l,r,k,v;
	REP(i,1,m)
	{
		scanf("%d",&opt);
		if(opt==1)
		{
			scanf("%d%d%d",&l,&r,&k);
			UpdataAdd(l,r,k);
		}
		if(opt==2)
		{
			scanf("%d%d%d",&l,&r,&v);
			UpdataMin(l,r,v);
		}
		if(opt==3)
		{
			scanf("%d%d",&l,&r);
			printf("%lld
",QuerySum(l,r));
		}
		if(opt==4)
		{
			scanf("%d%d",&l,&r);
			printf("%lld
",QueryMax(l,r));
		}
		if(opt==5)
		{
			scanf("%d%d",&l,&r);
			printf("%lld
",QueryHistoryMax(l,r));
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/Sxy_Limit/p/12651746.html