「CF803G Periodic RMQ Problem」

题目大意

给出一个长度为 \(n\) 的序列 \(b\),序列 \(a\) 又序列 \(b\) 复制 \(k\) 次后得到,需要支持区间覆盖,区间取 \(min\).

分析

开始没有想到可以用st表,很自闭.

但是毕竟写出来了,就来分享一个不用st表的做法,这个做法可能比较复杂,如果想要实现更方便的做法可以看看\(\color{red}{\sf{M}}\color{black}{\sf{eatherm}}\)神仙的题解.

可以发现如果暴力建树的复杂度是 \(\mathcal{O}(nk\log_2nk)\) 显然会 \(T\),开始的时候每连续 \(n\) 个都是相同的,所以考虑建 \(k\) 颗树,每颗树就是一个块,开始时都和初始树(用 \(b\) 建成的树)相同.

当然这里需要动态开点.

然后是修改操作,考虑将修改部分变成三段,分别为左端点所在的块,右端点所在的块,中间的块,然后对于每一段进行修改.

  1. 对于左右的块,直接在左右块对于的线段树上修改就可以了
  2. 对于中间的块,显然不可以暴力修改,那么考虑再用一颗线段树维护

然后是查询操作,仍然将它分成三段来查询,和修改的方式差不多.

但是在查询左右块时需要查询左右快是否被覆盖,如果被覆盖那么在维护所有块的线段树中将这个标记去掉,再进行全局覆盖.

时间复杂度 \(\mathcal{O}(m(log_2n+log_2k))\).

这就是线段树优化分块吗,i了i了.

代码

#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=1e5+7;
const int INF=1e9;
int n,m,k;
int arr[MAXN];
int root[MAXN];
int min_num=INF;
namespace All//维护块的线段树
{
	struct LazyTag//有覆盖标记
	{
		int if_cover;
		int cover;
		void Clean()
		{
			if_cover=0;
		}
	}for_make;
	LazyTag MakeTag(int cover)
	{
		for_make.if_cover=1;
		for_make.cover=cover;
		return for_make;
	}
	struct SegmentTree
	{
		int min;//需要维护区间min
		int cover;//需要维护这个位置的块是否被覆盖
		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)//合并区间min
	{
		sgt[now].min=min(sgt[LSON].min,sgt[RSON].min);
	}
	void Build(int now=1,int left=1,int right=k)//建树
	{
		if(left==right)
		{
			sgt[now].min=min_num;//开始都是区间中的最小数
			sgt[now].cover=0;
			return;
		}
		Build(LEFT);
		Build(RIGHT);
		PushUp(now);
	}
	void Down(LazyTag tag,int now)//修改
	{
		if(!tag.if_cover)
		{
			return;
		}
		sgt[now].tag.if_cover=1;//有覆盖表紧
		sgt[now].tag.cover=tag.cover;
		sgt[now].min=sgt[now].cover=tag.cover;//区间min和覆盖都赋值为cover
	}
	void PushDown(int now)//下传标记
	{
		Down(sgt[now].tag,LSON);
		Down(sgt[now].tag,RSON);
		sgt[now].tag.Clean();
	}
	void Cover(int now_left,int now_right,int cover,int now=1,int left=1,int right=k)//区间覆盖
	{
		if(now_right<left||right<now_left)
		{
			return;
		}
		if(now_left<=left&&right<=now_right)
		{
			Down(MakeTag(cover),now);
			return;
		}
		PushDown(now);
		Cover(NOW,cover,LEFT);
		Cover(NOW,cover,RIGHT);
		PushUp(now);
	}
	void UpdataMin(int place,int min,int now=1,int left=1,int right=k)//单点修改最小值,在左右块被修改后最小值也可能会发生改变
	{
		if(place<left||right<place)
		{
			return;
		}
		if(left==right)
		{
			sgt[now].min=min;
			return;
		}
		PushDown(now);
		UpdataMin(place,min,LEFT);
		UpdataMin(place,min,RIGHT);
		PushUp(now);
	}
	int QueryMin(int now_left,int now_right,int now=1,int left=1,int right=k)//查询区间min
	{
		if(now_right<left||right<now_left)
		{
			return INF;
		}
		if(now_left<=left&&right<=now_right)
		{
			return sgt[now].min;
		}
		PushDown(now);
		int result=min(QueryMin(NOW,LEFT),QueryMin(NOW,RIGHT));
		return result;
	}
	int Query(int place,int now=1,int left=1,int right=k)//单点查询这个块是否被覆盖
	{
		if(place<left||right<place)
		{
			return 0;
		}
		if(left==right)
		{
			int result=sgt[now].cover;
			sgt[now].cover=0;//相当于下传,所以需要清空
			return result;
		}
		PushDown(now);
		return Query(place,LEFT)+Query(place,RIGHT);
	}
	#undef LSON
	#undef RSON
	#undef MIDDLE
	#undef LEFT
	#undef RIGHT
	#undef NOW
}
namespace Range//维护区间的线段树
{
	int first_tree=0;//表示编号小于first_tree的节点为初始线段树上的
	struct LazyTag
	{
		int if_cover;
		int cover;
		void Clean()
		{
			if_cover=0;
		}
	}for_make;
	LazyTag MakeTag(int cover)
	{
		for_make.if_cover=1;
		for_make.cover=cover;
		return for_make;
	}
	struct SegmentTree
	{
		int min;
		int lson,rson;
		LazyTag tag;
	}sgt[MAXN*64];
	int sgt_cnt=0;
	#define LSON sgt[now].lson
	#define RSON sgt[now].rson
	#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].min=min(sgt[LSON].min,sgt[RSON].min);
	}
	void Build(int &now,int left=1,int right=n)//建树
	{
		now=++sgt_cnt;
		if(left==right)
		{
			sgt[now].min=arr[left];
			return;
		}
		Build(LEFT);
		Build(RIGHT);
		PushUp(now);
		if(left==1&&right==n)
		{
			first_tree=sgt_cnt;
		}
	}
	void Down(LazyTag tag,int &now)
	{
		if(!tag.if_cover)
		{
			return;
		}
		if(now<first_tree)//如果当前节点在初始线段树上那么就新建一个节点,需要将左右子节点也赋值过来
		{
			sgt[++sgt_cnt].lson=sgt[now].lson;
			sgt[sgt_cnt].rson=sgt[now].rson;
			now=sgt_cnt;
		}
		sgt[now].min=tag.cover;
		sgt[now].tag.if_cover=1;
		sgt[now].tag.cover=tag.cover;
	}
	void PushDown(int now)
	{
		Down(sgt[now].tag,LSON);
		Down(sgt[now].tag,RSON);
		sgt[now].tag.Clean();
	}
	void Cover(int now_left,int now_right,int cover,int &now,int left=1,int right=n)
	{
		if(now_right<left||right<now_left)
		{
			return;
		}
		if(now<first_tree)//同理建新节点
		{
			sgt[++sgt_cnt].lson=sgt[now].lson;
			sgt[sgt_cnt].rson=sgt[now].rson;
			now=sgt_cnt;
		}
		if(now_left<=left&&right<=now_right)
		{
			Down(MakeTag(cover),now);
			return;
		}
		PushDown(now);
		Cover(NOW,cover,LEFT);
		Cover(NOW,cover,RIGHT);
		PushUp(now);
	}
	int QueryMin(int now_left,int now_right,int now,int left=1,int right=n)//区间取min
	{
		if(now_right<left||right<now_left)
		{
			return INF;
		}
		if(now_left<=left&&right<=now_right)
		{
			return sgt[now].min;
		}
		PushDown(now);
		return min(QueryMin(NOW,LEFT),QueryMin(NOW,RIGHT));
	}
	#undef LSON
	#undef RSON
	#undef MIDDLE
	#undef LEFT
	#undef RIGHT
	#undef NOW
}
int main()
{
	scanf("%d%d",&n,&k);
	REP(i,1,n)
	{
		scanf("%d",&arr[i]);
		min_num=min(min_num,arr[i]);
	}
	Range::Build(root[0]);
	All::Build();
	REP(i,1,k)
	{
		root[i]=root[0];
	}
	scanf("%d",&m);
	int opt,l,r,x;
	int l_,r_;
	int l_c,r_c;
	int answer=0;
	REP(i,1,m)
	{
		scanf("%d%d%d",&opt,&l,&r);
		l_=l/n+(bool)(l%n);//l和r所在块的编号
		r_=r/n+(bool)(r%n);
		if(opt==1)//修改操作
		{
			scanf("%d",&x);
			if(l_+1<=r_-1)//如果中间存在块,那么中间的块赋值为x
			{
				All::Cover(l_+1,r_-1,x);
			}
			l_c=All::Query(l_);//查询左右的块是否被覆盖,如果覆盖那么就直接修改
			if(l_c)
			{
				Range::Cover(1,n,l_c,root[l_]);
			}
			r_c=All::Query(r_);
			if(r_c)
			{
				Range::Cover(1,n,r_c,root[r_]);
			}
			if(l_^r_)//如果左右所在不同
			{
                //可以画个图理解一下
				Range::Cover(l-(l_-1)*n,n,x,root[l_]);
				Range::Cover(1,r-(r_-1)*n,x,root[r_]);
			}
			else//如果在同一个快
			{
				Range::Cover(l-(l_-1)*n,r-(r_-1)*n,x,root[l_]);
			}
            //在左右的块中修改
			All::UpdataMin(l_,Range::QueryMin(1,n,root[l_]));
			All::UpdataMin(r_,Range::QueryMin(1,n,root[r_]));
		}
		if(opt==2)
		{
			answer=INF;
			if(l_+1<=r_-1)//如果中间有快那么查询中间快的min
			{
				answer=All::QueryMin(l_+1,r_-1);
			}
			l_c=All::Query(l_);
			if(l_c)//和修改部分同理
			{
				Range::Cover(1,n,l_c,root[l_]);
			}
			r_c=All::Query(r_);
			if(r_c)
			{
				Range::Cover(1,n,r_c,root[r_]);
			}
			if(l_^r_)//查询也需要分类讨论
			{
				answer=min(answer,min(Range::QueryMin(l-(l_-1)*n,n,root[l_]),Range::QueryMin(1,r-(r_-1)*n,root[r_])));
			}
			else
			{
				answer=min(answer,Range::QueryMin(l-(l_-1)*n,r-(r_-1)*n,root[l_]));
			}
			printf("%d\n",answer);
		}
	}
	return 0;
}
上一篇:c# 计算地球上两点间距离


下一篇:Celery