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(lognn∗log(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}} n1,所以期望是 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 n−i+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=n2∑i=1nin×(n−i+1)=n∑i=1ni1×(n−i+1)<n∑i=1nin=nnlnn=lnn
所以单次询问复杂度期望下比
O
(
ln
n
)
O(\ln n)
O(lnn) 要小,再加上常数极小,所以加个O2能过。
n 方 过 两 千 万 , 暴 力 踩 标 算 !
\text{n 方 过 两 千 万 , 暴 力 踩 标 算 !}
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;
}