洛谷P3793 由乃救爷爷——关于随机和静态RMQ

P3793 由乃救爷爷

前言

这题本应该是一个 O ( n ) ∼ O ( n log ⁡ log ⁡ n ) O(n)\sim O(n\log\log n) O(n)O(nloglogn) 预处理ST表的板题,但是稳定做法竟被乱搞做法吊打。

题解

稳定做法1:O(nloglogn)预处理,O(1)查询

分块+ST表,这是我能想到的和打得出来的最快的稳定做法了。

把数组分为大小为 log ⁡ n \log n logn 的块, O ( n ) O(n) O(n) 预处理出每个块内最大值,然后对这些整块的最大值做ST表,复杂度 O ( n log ⁡ n ∗ log ⁡ ( n log ⁡ n ) ) < O ( n ) O(\frac{n}{\log n}*\log(\frac{n}{\log n}))< O(n) O(lognnlog(lognn))<O(n)

然后对于每个询问区间,我们就可以 O ( 1 ) O(1) O(1) 算出它跨越的所有整块区间内的最大值。

然后还剩下两端长度小于 log ⁡ n \log n logn 的小区间,于是我们再做一个小ST表,以便在 O ( 1 ) O(1) O(1) 时间内求出每个小区间的最大值。这个ST表只处理每个点往后长度不超过 log ⁡ n \log n logn 的区间的最大值,所以复杂度是 O ( n log ⁡ log ⁡ n ) O(n\log\log n) O(nloglogn)

稳定做法2:O(n)预处理,O(1)查询

这个NB做法是从这篇文章看来的。

大概也是分成大小 log ⁡ n \log n logn 的块,然后大块间做ST表,预处理每个块内的前缀后缀最大值,总共也是 O ( n ) O(n) O(n)

剩下询问在一个块内的情况,对每个块做从左到右的递增的单调栈。如果我们要询问一块内 l l l r r r 之间的最小值,只需要求第 r r r 个时刻所对应的单调栈中第一个 ≥ l ≥l l 的下标就行。这个过程好像怎么也做不到 O ( 1 ) O(1) O(1),但是可以用神奇而看不懂的位运算做到 O ( 1 ) O(1) O(1),具体我也不知道为什么,可以看这里了解。

这是最快的稳定做法。

乱搞做法1:O(n)预处理,期望O(1)查询

这个不用多说了吧,就是建立一棵笛卡尔树,把最大值转化为求 L C A \rm LCA LCA

这个方法查询期望时间是 O ( 1 ) O(1) O(1) 的,此题数据随机,所以可过。

乱搞做法2:O(n)预处理,期望O(1)查询

把数组分成 n \sqrt{n} n 个块,暴力维护块到块的最大值和每个块内的前缀后缀最大值,这样对于跨块的询问可以 O ( 1 ) O(1) O(1) 解决。

然后就是最赞美太阳的地方:对于区间两端点在同一个块内的询问,暴力 O ( n ) O(\sqrt{n}) O(n ) 处理。

说明一下为什么可过:询问在一个块内的处理时间是 O ( n ) O(\sqrt{n}) O(n ),但是出现这种情况的概率大约为 1 n \frac{1}{\sqrt{n}} n 1,所以期望是 O ( 1 ) O(1) O(1) 的。

乱搞做法3:O(n)预处理,期望O(ln n)查询

我无比地赞美这个做法,以其简单粗暴和内涵深刻,令所有人汗颜。

先把原数组排序,这个用鸡排可以做到 O ( n ) O(n) O(n)

然后只需要从大到小枚举第一个出现在询问区间内的数即可。

理论上最大复杂度是 O ( n 2 ) O(n^2) O(n2),证明一下此题为什么能过:

对于一个区间大小为 k k k 的询问的期望时间为 n k \frac{n}{k} kn,总共大约有 n 2 n^2 n2 个区间,大小为 i i i 的区间有 n − i + 1 n-i+1 ni+1 个,所以询问一次的期望复杂度可以表示为

t = ∑ i = 1 n n i × ( n − i + 1 ) n 2 = ∑ i = 1 n 1 i × ( n − i + 1 ) n < ∑ i = 1 n n i n = n ln ⁡ n n = ln ⁡ n t=\frac{\sum_{i=1}^n\frac{n}{i}\times (n-i+1)}{n^2}\\ =\frac{\sum_{i=1}^n\frac{1}{i}\times (n-i+1)}{n}\\ <\frac{\sum_{i=1}^n\frac{n}{i}}{n} =\frac{n\ln n}{n}=\ln n t=n2i=1nin×(ni+1)=ni=1ni1×(ni+1)<ni=1nin=nnlnn=lnn

所以单次询问复杂度期望下比 O ( ln ⁡ n ) O(\ln n) O(lnn) 要小,再加上常数极小,所以加个O2能过。
n 方 过 两 千 万 , 暴 力 踩 标 算 ! \text{n 方 过 两 千 万 , 暴 力 踩 标 算 !}            

代码

稳定做法1:

#include<cstdio>//JZM yyds!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<ctime>
#include<stack>
#define ll long long
#define uns unsigned
#define MAXN 20000005
#define MAXB 850005
#define INF 1e18
#define IF it->first
#define IS it->second
#define lowbit(x) ((x)&(-(x)))
using namespace std;
/*inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}*/
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 B=24;
int n,m,s,k,LG[MAXB];
int b[MAXB][21],a[MAXN][5];
inline int BK(int x){return (x+B-1)/B;}
inline ll rmq(int l,int r){
	if(l>r)return 0;int k=LG[r-l+1];
	return max(a[l][k],a[r-(1<<k)+1][k]);
}
inline ll RMQ(int l,int r){
	if(l>r)return 0;int k=LG[r-l+1];
	return max(b[l][k],b[r-(1<<k)+1][k]);
}
uns ll ans;
signed main()
{
	scanf("%d%d%d",&n,&m,&s);
	Srand(s);
	for(int i=1;i<=n;i++)a[i][0]=read();
	for(int i=n;i>0;i--)
		for(int j=1;j<=4&&i+(1<<j)-1<=n;j++)
			a[i][j]=max(a[i][j-1],a[i+(1<<(j-1))][j-1]);
	for(int i=1;i<=n;i++)b[BK(i)][0]=max(b[BK(i)][0],a[i][0]);
	k=BK(n);
	for(int i=k;i>0;i--)
		for(int j=1;i+(1<<j)-1<=k;j++)
			b[i][j]=max(b[i][j-1],b[i+(1<<(j-1))][j-1]);
	for(int i=2;i<=max(k,B);i++)LG[i]=LG[i>>1]+1;
	for(int i=1;i<=m;i++){
		int l=read()%n+1,r=read()%n+1;
		if(l>r)swap(l,r);
		int c=BK(l),d=BK(r),p=c*B,q=d*B-B+1;
		if(c==d)ans+=rmq(l,r);
		else ans+=max(rmq(l,p),max(RMQ(c+1,d-1),rmq(q,r)));
	}
	printf("%llu\n",ans);
	return 0;
}

乱搞做法2(by PPL):

//12252024832524
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
typedef unsigned long long ULL;
const int MAXN = 20000005;
const int MAXB = 4475;
int n,m,B,M;
ULL ans;

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

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;
}

int pre[MAXB][MAXB],suf[MAXB][MAXB],MAX[MAXB][MAXB],a[MAXN],ID[MAXN];

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read(); m = Read();
	Srand(Read());
	B = ceil(sqrt(n)); M = (n-1) / B;
	for(int i = 0;i < n;++ i) a[i] = read(),ID[i] = i % B;
	for(int i = 0;i <= M;++ i)
	{
		int l = i*B,r = Min(n-1,(i+1)*B-1);
		pre[i][0] = a[l];
		for(int j = l+1;j <= r;++ j) pre[i][ID[j]] = Max(pre[i][ID[j-1]],a[j]);
		suf[i][ID[r]] = a[r];
		for(int j = r-1;j >= l;-- j) suf[i][ID[j]] = Max(suf[i][ID[j+1]],a[j]);
	}
	for(int i = 0;i <= M;++ i)
	{
		MAX[i][i] = suf[i][0];
		for(int j = i+1;j <= M;++ j)
			MAX[i][j] = Max(MAX[i][j-1],suf[j][0]);
	}
	for(int i = 1;i <= m;++ i)
	{
		int l = read()%n,r = read()%n;
		if(l > r) swap(l,r);
		int L = l/B,R = r/B,ret = 0;
		if(L == R){for(int j = l;j <= r;++ j) ret = Max(ret,a[j]);}
		else if(L+1 == R) ret = Max(suf[L][ID[l]],pre[R][ID[r]]);
		else ret = Max(Max(suf[L][ID[l]],pre[R][ID[r]]),MAX[L+1][R-1]);
		ans += ret;
	}
	Put(ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值