O(n)-O(1)のRMQ

Description

luoguP3793 由乃救爷爷
给出一个长度为n的序列,q次询问区间最值
n,q<=2*10^7

Solution

在不考虑读入的情况下,这里介绍一种O(n)预处理O(1)询问的做法
先把笛卡尔树搞出来,变成求LCA,这个东西告诉我们LCA也能做到O(n)-O(1)
求出欧拉序,变回区间最值,唯一不同的是这里的序列满足相邻两项的差为+1或-1
考虑分块,设块大小k=log(n)/2,
对于每一块求最值,块之间搞ST表,这里是O(n/k*log n)=O(n)
对于块内的,注意到每一个块差分之后都可以被表示为一个+1和-1的序列,总共只有2^k= n \sqrt n n 种不同的序列,对于每种序列求出每个子序列的最小值的位置,复杂度O( 2 k k 2 2^kk^2 2kk2)=O( n l o g 2 n \sqrt nlog^2 n n log2n)
然后询问就可以做到O(1)了
然而这个东西貌似没什么用。。。。
因为你需要考虑读入,然后读入的复杂度已经是O(n log n)了。。。。
一般来说读入都是用一些奇怪的种子来随机化
这样就有一个取巧的做法,不考虑后面的那部分,直接暴力
考虑两个端点在同一个块的概率为(k/n)^2,贡献为k,那么期望复杂度为 O ( k 2 n ) O({k^2\over n}) O(nk2)那么我们就可以直接调块大小来卡常了_(:з」∠)_
然后不知道为什么跑的贼慢死活卡不过去
还有一种是直接暴力在笛卡尔树上跑的做法,然后直接过了?!

Code

#include 
#include 
#include 
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

namespace GenHelper {
	unsigned z1,z2,z3,z4,b;
	unsigned rand_() {
		b=((z1<<6)^z1)>>13;
		z1=((z1&4294967294U)<<18)^b;
		b=((z2<<2)^z2)>>27;
		z2=((z2&4294967288U)<<2)^b;
		b=((z3<<13)^z3)>>21;
		z3=((z3&4294967280U)<<7)^b;
		b=((z4<<3)^z4)>>12;
		z4=((z4&4294967168U)<<13)^b;
		return (z1^z2^z3^z4);
	}
}

void srand(unsigned x) {
	using namespace GenHelper;
	z1=x; z2=(~x)^0x233333333U; z3=x^0x1234598766U; z4=(~x)+51;
}

int read() {
	using namespace GenHelper;
	int a=rand_()&32767;
	int b=rand_()&32767;
	return a*32768+b;
}

const int N=2e7+5;

int n,m,seed,a[N],R[N],L[N],sta[N],top;

int query(int l,int r) {
	int x=sta[1];
	for(;;x=(r<x?L:R)[x]) if (l<=x&&x<=r) return a[x];
}

int main() {
	scanf("%d%d%d",&n,&m,&seed);srand(seed);
	fo(i,1,n) a[i]=read();

	top=0;
	fo(i,1,n) {
		while (top&&a[sta[top]]<a[i]) L[i]=sta[top--];
		R[sta[top]]=i;sta[++top]=i;
	}

	unsigned long long ans=0;
	for(;m;m--) {
		int l=read()%n+1,r=read()%n+1;
		if (l>r) swap(l,r);
		ans+=query(l,r);
	}
	printf("%llu\n",ans);
	return 0;
}

你可能感兴趣的:(笛卡尔树)