(ST 表、博弈)洛谷 P8818 CSP-S 2022 策略游戏 题解
ST 表这种东西对我而言实在陌生。
题意
题目传送门,建议前往原题阅读题面和样例。
思路
小 L 先选一个 axa_xax,聪明的小 Q 肯定会:
- ax>0a_x>0ax>0,她必然选一个尽量小的 byb_yby;
- ax<0a_x<0ax<0,她必然选一个尽量大的 byb_yby。
ans=max(ans,max(ama*(ama>=0?bmi:bma),ami*(ami<0?bma:bmi)));
可是小 L 同样聪明,作为先手,这样对他并非最优。
反过来想,当小 Q 选的最小值 by>0b_y>0by>0,小 L 肯定想选最大的 axa_xax;如果不幸地 by<0b_y<0by<0,小 L 此时只能选正数,那么小 L 肯定想选一个最小的正数来止损。
反之,当小 Q 选的最大值 by<0b_y<0by<0,小 L 肯定想选最小的 axa_xax;如果不幸地 by>0b_y>0by>0,小 L 此时只能选负数,那么小 L 肯定想选一个最大的负数来止损。
反过来想怎么保证正确性?把这些步骤正过来在模拟一遍,顺便再理一下所有步骤:
-
小 L 直接选最大值或最小值:
- 小 L 选了一个最大值 ax>0a_x>0ax>0,小 Q 无脑选最小值,否则最大值 ax<0a_x<0ax<0 选最大值;
- 小 L 选了一个最小值 ax<0a_x<0ax<0,小 Q 无脑选最大值,否则最小值 ax>0a_x>0ax>0 选最小值。
-
小 L 选了正数最小值或负数最大值:
- 小 L 选了正数最小值,小 Q 选最小值;
- 小 L 选了负数最大值,小 Q 选最大值。
如上过程均符合博弈过程,能够保证小 L 的操作让自己尽可能优。
ans=max(ans,max(ama*(ama>=0?bmi:bma),ami*(ami<0?bma:bmi)));
if(zsmi!=inf)ans=max(ans,zsmi*bmi);
if(fsma!=-inf)ans=max(ans,fsma*bma);
现在考虑如何获取:区间最大值、区间最小值、区间最小正数、区间最大负数。正常来说,这个东西就是线段树板子,O(nlogn+mlogm+q(logn+logm))O(n\log n+m\log m+q(\log n+\log m))O(nlogn+mlogm+q(logn+logm)) 建树加查询,对于 n,qn,qn,q 都是 10510^5105 绰绰有余,而且这题空间限制较宽松。
但是这个东西又不带修,用线段树还是不够优。其实有一个数据结构,可以时空复杂度 O(nlogn)O(n\log n)O(nlogn) 预处理区间各种最值,O(1)O(1)O(1) 查询—— ST 表(RMQ),用以处理静态区间最值问题的高效算法(数据结构)。ST 表的原理实则是倍增,十分巧妙。
设 fi,jf_{i,j}fi,j 表示,区间 [i,i+2j−1][i,i+2^j-1][i,i+2j−1] 的最大值。有了倍增求 lca 的经验,我们学会把 fi,jf_{i,j}fi,j 表示的 [i,i+2j−1][i,i+2^j-1][i,i+2j−1] 拆成 [i,i+2j−1−1],[i+2j−1,i+2j−1][i,i+2^{j-1}-1],[i+2^{j-1},i+2^j-1][i,i+2j−1−1],[i+2j−1,i+2j−1] 两个子问题,这两个区间分别代表状态 fi,j−1,fi+2j−1,j−1f_{i,j-1},f_{i+2^{j-1},j-1}fi,j−1,fi+2j−1,j−1,于是:
fi,j=max(fi,j−1,fi+2j−1,j−1)f_{i,j}=\max(f_{i,j-1},f_{i+2^{j-1},j-1})fi,j=max(fi,j−1,fi+2j−1,j−1)
初始化 fi,0=aif_{i,0}=a_ifi,0=ai,aaa 即需要处理的数组。
开 666 个 ST 表维护 a,ba,ba,b 的最大最小值、aaa 的正数最小值、负数最大值即可。于是时间复杂度来到 O(nlogn+mlogm+q)O(n\log n+m\log m+q)O(nlogn+mlogm+q)。
其实这里还没体现出 ST 表的真正优势呢。
具体细节见代码。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e5+9,M=17,inf=1e18;
ll n,m,Q;
ll a[N],b[N];
ll fama[N][M],fami[N][M];//a数组 max,min
ll fzsmi[N][M],ffsma[N][M];//a数组 正数min,负数max
ll fbma[N][M],fbmi[N][M];//b数组 max,min
ll lg2[N];
void init()//预处理log2,免得多一个log
{lg2[1]=0;lg2[2]=1;for(int i=3;i<N;i++)lg2[i]=lg2[i/2]+1;
}
int main()
{scanf("%lld%lld%lld",&n,&m,&Q);init();for(int i=1;i<=n;i++){scanf("%lld",&a[i]);fama[i][0]=fami[i][0]=a[i];fzsmi[i][0]=(a[i]>=0?a[i]:inf);ffsma[i][0]=(a[i]<0?a[i]:-inf);}for(int i=1;i<=m;i++){scanf("%lld",&b[i]);fbma[i][0]=fbmi[i][0]=b[i];}for(int j=1;j<=lg2[n];j++){for(int i=1;i+(1<<j)-1<=n;i++){fama[i][j]=max(fama[i][j-1],fama[i+(1<<(j-1))][j-1]);fami[i][j]=min(fami[i][j-1],fami[i+(1<<(j-1))][j-1]);fzsmi[i][j]=min(fzsmi[i][j-1],fzsmi[i+(1<<(j-1))][j-1]);ffsma[i][j]=max(ffsma[i][j-1],ffsma[i+(1<<(j-1))][j-1]);}}for(int j=1;j<=lg2[m];j++){for(int i=1;i+(1<<j)-1<=m;i++){fbma[i][j]=max(fbma[i][j-1],fbma[i+(1<<(j-1))][j-1]);fbmi[i][j]=min(fbmi[i][j-1],fbmi[i+(1<<(j-1))][j-1]);}}while(Q--){ll l1,r1,l2,r2,ans=-inf;scanf("%lld%lld%lld%lld",&l1,&r1,&l2,&r2);ll s1=lg2[r1-l1+1],s2=lg2[r2-l2+1];ll bma=max(fbma[l2][s2],fbma[r2-(1<<s2)+1][s2]);ll bmi=min(fbmi[l2][s2],fbmi[r2-(1<<s2)+1][s2]);ll ama=max(fama[l1][s1],fama[r1-(1<<s1)+1][s1]);ll ami=min(fami[l1][s1],fami[r1-(1<<s1)+1][s1]);ll zsmi=min(fzsmi[l1][s1],fzsmi[r1-(1<<s1)+1][s1]);ll fsma=max(ffsma[l1][s1],ffsma[r1-(1<<s1)+1][s1]);ans=max(ans,max(ama*(ama>=0?bmi:bma),ami*(ami<0?bma:bmi)));if(zsmi!=inf)ans=max(ans,zsmi*bmi);if(fsma!=-inf)ans=max(ans,fsma*bma);printf("%lld\n",ans); }return 0;
}