CF484E Sign on Fence
\(\texttt{Description}\)
给定一个长度为 \(n\) 的数列 \(A\),有 \(m\) 次询问,每次在给出 \(A_l\sim A_r\) 中选一个长度为 \(k\) 的子区间,使得选出区间的最小值最大。
\(1\le n,m\le 10^5\),\(1\le A_i\le 10^9\)。
\(\texttt{Solution}\)
首先看到最小值最大,不难想到二分。由于是多组询问,所以考虑整体二分。
在整体二分中,我们套路地将所有询问当前答案的值域 \([L,R]\) 分成 \([L,\text{mid}]\) 和 \((\text{mid},R]\) 两部分。 先二分一个 \(\text{mid}\),然后维护所有 \(> \text{mid}\) 的数的最长连续子区间长度。每次询问查询 \([l,r]\) 这一区间的答案。若结果 \(\ge k\),说明答案在 \((\text{mid},R]\) 中,否则在 \([L,\text{mid}]\) 中。然后继续对这两部分递归求解即可。若 \(L=R\),则更新当前递归内的所有询问的答案。
具体来讲,可以参照洛谷 P4513 的思路,使用线段树维护所有 \(> \text{mid}\) 的数的最长连续子区间长度,即把所有 \(>\text{mid}\) 的数的贡献统计到线段树上。考虑在线段树的每个节点中维护 \(\text{len},\text{llen},\text{rlen},\text{sz}\) 四个值,表示该节点的答案、左起最长连续段长度、右起最长连续段长度和该节点对应的区间长度。
令 \(x\) 为当前节点,\(\text{ls}\) 为左儿子,\(\text{rs}\) 为右儿子。则有:
-
若 \(\text{llen}_{\text{ls}}\ne \text{sz}_\text{ls}\),说明左起最长连续段不横跨左右儿子的区间,\(\text{llen}_x=\text{llen}_{\text{ls}}\);否则说明横跨左右儿子的区间,\(\text{llen}_x=\text{sz}_\text{ls}+\text{llen}_\text{rs}\)。\(\text{rlen}_x\) 类似维护即可。
-
\(\text{sz}_x=\text{sz}_\text{ls}+\text{sz}_\text{rs}\)。显而易见。
-
\(\text{len}_x=\max\{\text{len}_\text{ls},\text{len}_\text{rs},\text{rlen}_\text{ls}+\text{llen}_\text{rs}\}\)。表示分别考虑答案完全在左儿子区间内、完全在右儿子区间内和横跨左右儿子的区间。
此外,这题并不像模板题一样答案符合可加性,因为模板题只有数量的限制,可以加加减减。但是这题还有连续的限制,就不能像模板题那样,若查询结果为 \(p\) 且 \(p<k\),则在 \([L,\text{mid}]\) 内查找剩下的 \(k-p\) 个数。但是若暴力往两个区间内插入 \(>\text{mid}\) 的数,肯定会 TLE
。由于 \(>\text{mid}\) 的数在 \([L,\text{mid}]\) 仍有贡献(用它连接起两端的连续段,符合连续的限制),所以可以考虑先递归求解 \([L,\text{mid}]\) 的答案,再消除当前递归中的数在线段树中的贡献,再递归求解 \((\text{mid},R]\)。注意,在递归求解 \([L,\text{mid}]\) 的答案时,询问内的 \(k\) 值不能改成 \(k-p\),因为那 \(p\) 个值仍然在线段树上有贡献。这样就可以在保证效率的同时实现连续的限制了。
令 \(V\) 为值域(上界减下界),这样的方法时间复杂度和空间复杂度均为 \(\mathcal{O}((n+m)\cdot \log n\cdot \log V)\),可以接受。
\(\texttt{Code}\)
实现细节:
-
我是用
vector
储存整体二分中的数与询问的。 -
使用指令集或离散化(嫌麻烦没写 qwq)可以加快代码运行效率。
$\texttt{Click for Code}$
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
#define ls x*2
#define rs x*2+1
const int N=100005;
int n,m,ans[N];
struct queries{
int l,r,k,id;
};
vector<queries>Q;
struct num{
int x,p;
};
vector<num>A;
struct tree{
int len,llen,rlen,sz;
}sg[N*4];
void up(int x){
sg[x].sz=sg[ls].sz+sg[rs].sz;
sg[x].len=max({sg[ls].len,sg[rs].len,sg[ls].rlen+sg[rs].llen});
sg[x].llen=(sg[ls].len==sg[ls].sz?sg[ls].len+sg[rs].llen:sg[ls].llen);
sg[x].rlen=(sg[rs].len==sg[rs].sz?sg[rs].len+sg[ls].rlen:sg[rs].rlen);
}
void build(int x,int l,int r){
if(l==r){
sg[x]={0,0,0,1};
return;
}
int mid=l+r>>1;
build(ls,l,mid);
build(rs,mid+1,r);
up(x);
}
void change(int x,int l,int r,int k,int v){
if(l==r&&l==k){
sg[x]={v,v,v,1};
return;
}
int mid=l+r>>1;
if(k<=mid){
change(ls,l,mid,k,v);
}else{
change(rs,mid+1,r,k,v);
}
up(x);
}
tree query(int x,int l,int r,int ql,int qr){
if(ql<=l&&qr>=r){
return sg[x];
}
int mid=l+r>>1;
if(qr<=mid){
return query(ls,l,mid,ql,qr);
}else if(ql>mid){
return query(rs,mid+1,r,ql,qr);
}else{
tree t1=query(ls,l,mid,ql,qr),t2=query(rs,mid+1,r,ql,qr);
return {max({t1.len,t2.len,t1.rlen+t2.llen}),(t1.len==t1.sz?t1.len+t2.llen:t1.llen),(t2.len==t2.sz?t2.len+t1.rlen:t2.rlen),t1.sz+t2.sz};
}
}
void solve(vector<queries>q,vector<num>a,int l,int r){
if(!q.size()||!a.size()){
return;
}
if(l^r){
int mid=l+r>>1;
vector<queries>q1,q2;
vector<num>a1,a2;
for(auto i:a){
if(i.x>mid){
a1.push_back(i);
change(1,1,n,i.p,1);
}else{
a2.push_back(i);
}
}
for(auto i:q){
tree tmp=query(1,1,n,i.l,i.r);
if(tmp.len>=i.k){
q1.push_back(i);
}else{
q2.push_back(i);
}
}
solve(q2,a2,l,mid);
for(auto i:a){
if(i.x>mid){
change(1,1,n,i.p,0);
}
}
solve(q1,a1,mid+1,r);
}else{
for(auto i:q){
ans[i.id]=l;
}
}
}
signed main(){
cin>>n;
for(int i=1,x;i<=n;++i){
cin>>x;
A.push_back({x,i});
}
cin>>m;
for(int i=1,l,r,k;i<=m;++i){
cin>>l>>r>>k;
Q.push_back({l,r,k,i});
}
build(1,1,n);
solve(Q,A,1,1e9);
for(int i=1;i<=m;++i){
cout<<ans[i]<<'\n';
}
}