海压竹枝低复举,风吹山角晦还明|

CheZiHe929

园龄:1年10个月粉丝:9关注:4

P8818 [CSP-S 2022] 策略游戏 题解

P8818 [CSP-S 2022] 策略游戏 题解

题目链接

P8818 [CSP-S 2022] 策略游戏

简化题意

A 先在 a[l1,r1] 中选择一个数 x,小 B 再在 b[l2,r2] 中选择一个数 y,最后的分数就是 x×y

A 想让分数尽可能地大,而小 B 则想让分数尽可能地小,问最后的分数会是多少。

简要思路

分情况讨论:

  • x0 时,小 B 一定会选择一个最小的 y;

  • x<0 时,小 B 一定会选择一个最大的 y

再来考虑小 A 的选择情况,同样是分类讨论:

  • x0 时,因为小 B 会选择最小的 y,所以:

    • y0 时,小 A 会让 x 更大,因为正数 × 正数 = 正数;

    • y<0 时,小 A 会让 x 更小,即选择最小的非负数(因为现在讨论的是 x0 的情况),因为负数 × 正数 = 负数。

  • x<0 时,因为小 B 会选择最大的 y,所以:

    • y0 时,小 A 会让 x 更大,即选择最大的负数(同理,因为现在讨论的是 x<0 的情况),因为正数 × 负数 = 负数;

    • y<0 时,小 A 会让 x 更小,因为负数 × 负数 = 正数。

综上所述,小 A 对数的选择只有四种:

  1. 选择区间内最大的数;

  2. 选择区间内最小的非负数;

  3. 选择区间内最大的负数;

  4. 选择区间内最小的数。

因此我们只需要分别讨论得到小 A 的这四种选择在小 B 经过选择后的最优解即可。

而因为我们也要考虑小 B 对答案的最优解,所以我们要维护六个区间最值,即六个 ST 表:

  1. a 区间内的最大值;

  2. a 区间内的最小值;

  3. a 区间内的最小非负数(开始时将其赋值为一个极大值(因为维护时取最小值),从而方便判断该区间内是否有非负数);

  4. a 区间内的最大负数(同理,开始时赋为一个极小值(因为维护时取最大值),从而方便判断该区间内是否有负数);

  5. b 区间内的最大值;

  6. b 区间内的最小值。

剩下部分就是 ST 表的板子了,这里放个 ST 表的讲解:

数据结构之ST表 详解

完整代码

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
const int MAXN=1e5+5;
const int MAX_log=25;
const int MAX=LONG_LONG_MAX;//极值
int n,m,q;
int l1,r1,l2,r2;
int lg[MAXN];//log(i)=lg[i] 即 2^(lg[i])<=i
int a[MAXN],b[MAXN];
//6 个 ST 表(第一维起点,第二维长度[2^j])
int a_max[MAXN][MAX_log];//a 区间最大值
int a_min[MAXN][MAX_log];//a 区间最小值
int b_max[MAXN][MAX_log];//b 区间最大值
int b_min[MAXN][MAX_log];//b 区间最小值
int af_max[MAXN][MAX_log];//a 负数区间最大值
int az_min[MAXN][MAX_log];//a 非负数区间最小值
signed main(){
std::cin>>n>>m>>q;
//---------ST 表的预处理---------
for(int i=1;i<=n;i++){
std::cin>>a[i];
a_max[i][0]=a_min[i][0]=a[i];
if(a[i]<0){
af_max[i][0]=a[i];
az_min[i][0]=MAX;//暂时没有非负数
}else{
af_max[i][0]=-MAX;//暂时没有负数
az_min[i][0]=a[i];
}
}
for(int i=1;i<=m;i++){
std::cin>>b[i];
b_max[i][0]=b_min[i][0]=b[i];
}
//---------lg 数组的预处理---------
for(int i=2;i<=std::max(n,m)+2;i++)
lg[i]=lg[i/2]+1;
//---------倍增思想(分开)的维护
for(int j=1;j<=lg[n];j++)//枚举区间长度
for(int i=1;i+(1<<j)-1<=n;i++){//枚举区间起点(注意先长度再起点)
int now=i+(1<<(j-1));//第一个区间终点-1、第二个区间起点
a_max[i][j]=std::max(a_max[i][j-1],a_max[now][j-1]);
af_max[i][j]=std::max(af_max[i][j-1],af_max[now][j-1]);
a_min[i][j]=std::min(a_min[i][j-1],a_min[now][j-1]);
az_min[i][j]=std::min(az_min[i][j-1],az_min[now][j-1]);
//以 i 为起点 长度为 2^j
//以 i 为起点 长度为 2^(j-1)
//以 i+2^(j-1) 为起点 长度为 2^(j-1)
}
for(int j=1;j<=lg[m];j++)//区间长度
for(int i=1;i+(1<<j)-1<=m;i++){//区间起点
int now=i+(1<<(j-1));
b_max[i][j]=std::max(b_max[i][j-1],b_max[now][j-1]);
b_min[i][j]=std::min(b_min[i][j-1],b_min[now][j-1]);//同上
}
//---------ST 表的查询---------
//两个 2的幂 覆盖整个序列 a a ab ab b b
while(q--){
std::cin>>l1>>r1>>l2>>r2;
int alg=lg[r1-l1+1],blg=lg[r2-l2+1];//a,b 序列的长度 2^alg 2^blg
int sta=r1-(1<<alg)+1,stb=r2-(1<<blg)+1;//后一个区间起点=终点-区间长度+1
int A_max=std::max(a_max[l1][alg],a_max[sta][alg]);
int Af_max=std::max(af_max[l1][alg],af_max[sta][alg]);
int A_min=std::min(a_min[l1][alg],a_min[sta][alg]);
int Az_min=std::min(az_min[l1][alg],az_min[sta][alg]);
//前一个区间(以 l1 开头 长度为 alg)
//后一个区间(以 sta 开头,长度为 alg)
int B_max=std::max(b_max[l2][blg],b_max[stb][blg]);
int B_min=std::min(b_min[l2][blg],b_min[stb][blg]);
//前一个区间(以 l2 开头 长度为 blg)
//后一个区间(以 stb 开头,长度为 blg)
int maxn=-MAX;
//分类讨论正负性,考虑小 B 的最优选择
maxn=std::max(maxn,A_max*(A_max>=0?B_min:B_max));
maxn=std::max(maxn,A_min*(A_min>=0?B_min:B_max));
if(Af_max!=-MAX)maxn=std::max(maxn,Af_max*(Af_max>=0?B_min:B_max));//有负数才更新
if(Az_min!=MAX)maxn=std::max(maxn,Az_min*(Az_min>=0?B_min:B_max));//有非负数才更新
std::cout<<maxn<<endl;
}
return 0;
}
posted @   CheZiHe929  阅读(70)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起