CSP-S 2022 T2 题解
简述题意
有数列 \(a_1,a_2\ldots a_n\) 和 \(b_1,b_2\ldots b_m\),每次给出区间 \([l_1,r_1]\) 和 \([l_2,r_2]\),甲选择 \(x\in [l_1,r_1]\cap\mathbb{Z}\),乙选择 \(y\in[l_2,r_2]\cap\mathbb{Z}\),最后得分为 \(a_xb_y\),甲希望得分最大,乙希望得分最小,两人都足够聪明,\(q\) 次询问,每次给出 \([l_1,r_1],[l_2,r_2]\),问最终得分。
数据范围:\(1\le n,m,q\le 10^5,-10^9\le a_i,b_i\le 10^9\),所有数都是整数。
解题思路
可以假设是甲先选一个数,乙再选一个数使得甲选的数变小。假设甲选的数为 \(x\) 时乙的最优决策为 \(f(x)\),那么最后的答案就是 \(\max\{xf(x)\}\)。
不难发现乙可能的最优决策只有 \(O(1)\) 种,乙只能选择最小/大的非负/负数。
考虑证明:
在所有乙可以选择的数中,设最小的非负数为 \(b_{\min}\),最大的非负数为 \(b_{\max}\),绝对值最小的负数为 \(-b_{\min}\),绝对值最大的负数为 \(-b_{\max}\)。再设甲选择的数为 \(x\)。
情况一,\(x=0\)。
这时乙的选择并不重要,可以直接钦定乙选择 \(\{b_{\min},b_{\max},-b_{\min},-b_{\max}\}\) 中的一个,这不影响我们结论的正确性。
情况二,\(x>0\)。
如果乙可以选择负数,那么乙一定会选择 \(-b_{\max}\),否则乙一定选择 \(b_{\min}\)。
假如存在 \(xy<x(-b_{\max})\),结合 \(x>0\) 的假设可知 \(y<-b_{\max}\),与假设矛盾。选择 \(b_{\min}\) 时的证明同理。
情况三,\(x<0\)。
如果乙可以选择正数,那么乙一定会选择 \(b_{\max}\),否则乙一定选择 \(-b_{\min}\)。
仿照情况二的证明即可。
综上可知结论成立。
又发现甲乙的操作顺序并不重要,我们可以看做乙先选一个数,甲再选一个数使得得分最大。
交换操作顺序后的情况和交换之前是对称的,同样我们可以有:甲只有 \(O(1)\) 种可能的最优决策,只能选择最小/大的非负/负数。
于是只需要枚举甲的决策,判断乙对应的最优决策即可。
现在需要求出 \(b_{\min},b_{\max},-b_{\min},-b_{\max}\) 。这就是稍微变种的 RMQ 问题,我们考虑把非负数和负数分离开,分别用线段树或者 ST 表维护区间最值即可。
算法流程
- 用数据结构分别维护 \(a,b\) 上的区间最大/小的非负/负数。
- 对于每次询问,枚举甲可能的最优决策,判断乙对应的最优决策。
时间复杂度 \(O(n\log n+q)\) 或 \(O(n\log n+q\log n)\)。
代码
维护方便,我写了 \(8\) 颗线段树,这样就只有 \(2\) 种 pushup 的规则,数据结构部分比较好写,不过常数大了不少。
给出主要部分的代码:
const int N=1e5+5,M=2e6+5;
int n,m,q,a[N],b[N];
class node
{
public:
int c[2];
int val;
}; node s[M]; int cnt;
#define ls(x) s[x].c[0]
#define rs(x) s[x].c[1]
#define mid ((l+r)>>1)
#define L(x) ls(x),l,mid
#define R(x) rs(x),mid+1,r
void build(int &x,int l,int r)
{
if(!x) x=++cnt;
if(l==r) return;
build(L(x)),build(R(x));
}
inline void pushup(int x,bool tp)
{
if(tp) s[x].val=max(s[ls(x)].val,s[rs(x)].val);
else s[x].val=min(s[ls(x)].val,s[rs(x)].val);
}
void insert(int x,int l,int r,int p,int k,bool tp)
{
if(l==r&&l==p) {
s[x].val=k;
return;
}
if(p<=mid) insert(L(x),p,k,tp);
else insert(R(x),p,k,tp);
pushup(x,tp);
}
inline int merge(int x,int y,bool tp)
{
if(tp) return max(x,y);
else return min(x,y);
}
const int inf=2e9;
int query(int x,int l,int r,int ql,int qr,bool tp)
{
if(!x||l>qr||r<ql) return tp?-inf:inf;
if(l>=ql&&r<=qr) return s[x].val;
return merge(query(L(x),ql,qr,tp),query(R(x),ql,qr,tp),tp);
}
class segtree
{
public:
int rt,L,R;
bool mx;
inline void make(int l,int r) { L=l,R=r; build(rt,L,R); }
inline int ask(int l,int r) { return query(rt,L,R,l,r,mx); }
inline void ins(int p,int k) { insert(rt,L,R,p,k,mx); }
};
segtree amx,amn,aamx,aamn;
segtree bmx,bmn,bbmx,bbmn;
//依次维护最大非负数,最小非负数,绝对值最小负数,绝对值最大负数
inline void work()
{
int l1,r1,l2,r2;
ll as;
for(int i=1,ax,bx,an,bn,aax,bbx,aan,bbn;i<=q;++i)
{
as=LLONG_MIN;
read_(l1),read_(r1),read_(l2),read_(r2);
ax=amx.ask(l1,r1);
aax=aamx.ask(l1,r1);
an=amn.ask(l1,r1);
aan=aamn.ask(l1,r1);
bx=bmx.ask(l2,r2);
bbx=bbmx.ask(l2,r2);
bn=bmn.ask(l2,r2);
bbn=bbmn.ask(l2,r2);
if(ax!=-inf)
{
if(bbn!=inf) as=max(as,1ll*ax*bbn);
else as=max(as,1ll*ax*bn);
}
if(aax!=-inf)
{
if(bx!=-inf) as=max(as,1ll*aax*bx);
else as=max(as,1ll*aax*bbx);
}
if(an!=inf)
{
if(bbn!=inf) as=max(as,1ll*an*bbn);
else as=max(as,1ll*an*bn);
}
if(aan!=inf)
{
if(bx!=-inf) as=max(as,1ll*aan*bx);
else as=max(as,1ll*aan*bbx);
}
cout<<as<<'\n';
}
}
void init()
{
amx.make(1,n),amn.make(1,n);
amx.mx=1,amn.mx=0;
bmx.make(1,m),bmn.make(1,m);
bmx.mx=1,bmn.mx=0;
aamx.make(1,n),aamn.make(1,n);
aamx.mx=1,aamn.mx=0;
bbmx.make(1,m),bbmn.make(1,m);
bbmx.mx=1,bbmn.mx=0;
for(int i=1;i<=n;++i)
{
if(a[i]>=0) amx.ins(i,a[i]),amn.ins(i,a[i]),aamx.ins(i,-inf),aamn.ins(i,inf);
else amx.ins(i,-inf),amn.ins(i,inf),aamx.ins(i,a[i]),aamn.ins(i,a[i]);
}
for(int i=1;i<=m;++i)
{
if(b[i]>=0) bmx.ins(i,b[i]),bmn.ins(i,b[i]),bbmx.ins(i,-inf),bbmn.ins(i,inf);
else bmx.ins(i,-inf),bmn.ins(i,inf),bbmx.ins(i,b[i]),bbmn.ins(i,b[i]);
}
}
void solmain()
{
read_(n),read_(m),read_(q);
for(int i=1;i<=n;++i) read_(a[i]);
for(int i=1;i<=m;++i) read_(b[i]);
init();
work();
}
这题还是比较细的,考场上要耐心打一打。