#分块,懒标记#LOJ 3631「2021 集训队互测」学姐买瓜

题目传送门


分析

有一个很简单的做法就是处理出每个位置能够一次到达的最左边的右端点(后继)。

然后直接从 \(l\) 开始能跳就跳,这样单次询问时间复杂度是 \(O(n)\) 的。

观察到时间复杂度因为跳跃和处理右端点被浪费了。

可不可以一下子跳很多步,并且处理一堆右端点。

其实这两个需求是负相关的,需要平衡规划,那么让它们各自保持 \(\sqrt{n}\) 的复杂度就可以了。

\(n\) 个位置分块,维护后继、跳出块的后继、跳出块的步数(初始化为1),然后对于 \(l\) 之前的块打标记,对于 \(l\) 所在的块标记下传再修改。

询问的时候最多跳 \(\sqrt{n}\) 个块,所以总时间复杂度是 \(O(m\sqrt{n})\) 的。


代码

#include <cstdio>
#include <cctype>
#include <cmath>
using namespace std;
const int N=300011,inf=0x3f3f3f3f;
int L[N],R[N],n,m,bl,pos[N],dis[N],lazy[N],nxt[N],Nxt[N];
int iut(){
	int ans=0; char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=ans*10+c-48,c=getchar();
	return ans;
}
void print(int ans){
	if (ans>9) print(ans/10);
	putchar(ans%10+48);
}
int min(int a,int b){return a<b?a:b;}
int main(){
	m=iut(),n=iut(),bl=sqrt(n+1);
	for (int i=1;i<=n+1;++i) pos[i]=(i-1)/bl+1;
	for (int i=1;i<=n+1;++i){
		if (!L[pos[i]]) L[pos[i]]=i;
		R[pos[i]]=i,lazy[pos[i]]=inf;
	}
	for (int i=1;i<=n+1;++i) Nxt[i]=nxt[i]=inf,dis[i]=1;
	for (int i=1;i<=m;++i){
		int opt=iut(),l=iut(),r=iut(),t=pos[l];
		if (opt==1){
			for (int i=1;i<t;++i) lazy[i]=min(lazy[i],r+1);
			if (lazy[t]!=inf){
				for (int i=L[t];i<=R[t];++i)
				if (lazy[t]<nxt[i])
					nxt[i]=Nxt[i]=lazy[t],dis[i]=1;
				lazy[t]=inf;
			}
			if (t<pos[r+1]){
				for (int i=L[t];i<=l;++i)
				if (r+1<nxt[i])
					nxt[i]=Nxt[i]=r+1,dis[i]=1;
			}else for (int i=L[t];i<=l;++i)
			    if (r+1<nxt[i]) nxt[i]=r+1,dis[i]=dis[nxt[i]]+1;
			for (int i=R[t];i>=L[t];--i)
			if (nxt[i]<=R[t])
				dis[i]=dis[nxt[i]]+1,Nxt[i]=Nxt[nxt[i]];
		}else{
			int ans=0;
			while (1){
				int now=min(lazy[pos[l]],Nxt[l]);
				if (now<=r+1) ans+=dis[l],l=now;
				   else if (nxt[l]<=r+1) ++ans,l=nxt[l];
				       else break;
			}
			print(ans),putchar(10);
		}
	}
	return 0;
}
posted @ 2022-03-25 20:09  lemondinosaur  阅读(162)  评论(0编辑  收藏  举报