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
nlog2n)
然后询问就可以做到O(1)了
然而这个东西貌似没什么用。。。。
因为你需要考虑读入,然后读入的复杂度已经是O(n log n)了。。。。
一般来说读入都是用一些奇怪的种子来随机化
这样就有一个取巧的做法,不考虑后面的那部分,直接暴力
考虑两个端点在同一个块的概率为(k/n)^2,贡献为k,那么期望复杂度为
O
(
k
2
n
)
O({k^2\over n})
O(nk2)那么我们就可以直接调块大小来卡常了_(:з」∠)_
然后不知道为什么跑的贼慢死活卡不过去
还有一种是直接暴力在笛卡尔树上跑的做法,然后直接过了?!
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#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;
}