qoj970 Best Subsequence
题目传送门
题目大意
给定一个长度为 \(n\) 的序列,给定 \(m\) 次询问,每次询问给定 \(l,r,k\),求出序列 \(l\) 至 \(r\) 中长度为 \(k\) 的子序列相邻两项和的最大值,给出所有子序列答案的最小值。
整体思路
要使最大值最小,考虑二分答案。则问题转化为给定一个最大值 \(x\),求出序列 \(l\) 至 \(r\) 中是否有 \(k\) 项满足所有相邻两项和都小于等于 \(x\)。
有一个经典 trick 为:想要使相邻两项和小于等于 \(x\),可以设所有小于等于 \(\frac{x}{2}\) 的数为 \(1\),大于 \(\frac{x}{2}\) 的数为 \(0\)。
则原序列变为一个 \(01\) 序列,问题转化为选取一个长度为 \(k\) 的序列满足相邻两项不能都为 \(0\),若选取了一个 \(0\),则需要满足当前数加上相邻两个 \(1\) 的最大值小于等于 \(x\)。
可以发现如果选全部的 \(1\) 一定是最优的。假如有一个地方的 \(1\) 没有选,则选上他顶多会使一个 \(0\) 落选,一定不劣。
先将原数组离散化,显然如果 \(x\) 从小到大变化,顶多会使 \(01\) 序列中的 \(0\) 变化为 \(1\) 共 \(n\) 次,每次变化的位置一定不多,所以可以建立一棵主席树。二分的时候直接查询 \(x\) 版本下 \(l,r\) 的答案了,然后判断是否大于 \(k\),就可以进行二分了。
主席树维护
那主席树上维护什么呢?显然,\(l\) 至 \(r\) 区间内 \(1\) 的总个数是好求的,所以只需要计算造成贡献的 \(0\) 的个数即可。
因为 \(0\) 如果产生了贡献,则两边一定各有一个 \(1\),可以将 \(0\) 产生的贡献挂到左边的 \(1\) 上。
当主席树的版本增加的时候,会将一些 \(0\) 变成 \(1\)。设新出现的 \(1\) 的位置为 \(mid\),\(mid\) 之前的第一个 \(1\) 的位置为 \(last\),\(mid\) 之后的第一个 \(1\) 的位置为 \(next\)。则 \(a_{mid}=\min\limits_{last<i<next} a_i\),如果 \(last\) 至 \(next\) 中有 \(0\) 造成贡献,则一定是 \(mid\) 这个位置先造成贡献。
那什么时候 \(mid\) 这个位置会和 \(last\) 与 \(next\) 一起产生贡献呢?
设 \(A=\max(a_{last},a_{next}),B=a_{mid}\),当 \(A+B<=x<2 \times B\) 时,\(mid\) 会产生贡献。下界即为要求,因为相邻两项必定要小于等于 \(x\)。而上界则是 \(mid\) 这个位置还未变为 \(1\) 的时候才会产生关于 \(0\) 的贡献,一旦其变为 \(1\),他就直接会加上关于 \(1\) 的贡献了。
所以维护一个数组 \(b\),\(b_i\) 表示当 \(i\) 这个位置变为 \(1\) 的时候其与后面相邻的 \(1\) 的位置的值的最大值,即 \(A\)。每出现一个 \(1\),查询在其位置之前的上一个 \(1\) 的 \(b\) 值。然后在主席树的 \(b_{last}\) 至 \(2 \times a_i-1\) 版本的 \(last\) 位置上贡献加上 \(1\)。当然,每出现一个新的 \(1\),需要在主席树 \(2 \times a_i\) 至 \(\infty\) 的版本的 \(i\) 位置上贡献加上 \(1\)。同时修改 \(b_{last}\) 与 \(b_i\)。
而加贡献可以用差分来处理,将产生贡献的信息存到 vector 里面,进行一个排序,最后处理的时候边离散化,边加到主席树上。而查询位置上一个与下一个出现的 \(1\),即 \(last\) 与 \(next\),这个用 set 可以维护。
这样就解决了大部分问题,还有一个小问题。
二分 check
发现一个问题,现在似乎无法处理最后一个 \(1\) 与第一个 \(1\) 之间 \(0\) 的贡献。这是一个环,而我们只处理了链。
这个东西可以直接特判,求出两个 \(1\) 之间数字为 \(0\) 的最小值,将其加上两个 \(1\) 之间的最大值,看看是否小于等于 \(k\)。
注意求贡献的时候在求第一个 \(1\) 与最后一个 \(1\) 之间的贡献时,应该是 \(query(last,next-1)+1\),因为 \(next\) 位置的贡献记录的是 \(next\) 与其后面一个 \(1\) 中间 \(0\) 的贡献,有可能会超出 \(r\)。
求 \(last\) 与 \(next\) 还有求最小值显然可以再维护一棵线段树,里面维护区间最小值,求 \(last\) 与 \(next\) 进行线段树二分就可以了。其余详见代码。
ACcode
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INT_MAX (int)(2e9)
const int N=2e5+10;
int n,q,len;
int a[N],b[N],pai[N],val[N];
struct node{
int ban,wei,val;
};
vector<node> cha;
struct node1{
int tr[N<<2];
void pushup(int bian){
tr[bian]=min(tr[bian<<1],tr[bian<<1|1]);
}
void build(int bian,int l,int r){
if(l==r){tr[bian]=a[l];return;}
int mid=(l+r>>1);
build(bian<<1,l,mid);
build(bian<<1|1,mid+1,r);
pushup(bian);
}
int query(int bian,int l,int r,int L,int R){
if(L<=l&&R>=r) return tr[bian];
int ans=INT_MAX;
int mid=(l+r>>1);
if(L<=mid) ans=min(ans,query(bian<<1,l,mid,L,R));
if(R>mid) ans=min(ans,query(bian<<1|1,mid+1,r,L,R));
return ans;
}
int left_query(int bian,int l,int r,int x,int v){
if(tr[bian]>v) return 0;
if(l==r) return l;
int mid=(l+r>>1);
if(x<=mid) return left_query(bian<<1,l,mid,x,v);
int ans=left_query(bian<<1|1,mid+1,r,x,v);
if(ans!=0) return ans;
return left_query(bian<<1,l,mid,x,v);
}
int right_query(int bian,int l,int r,int x,int v){
if(tr[bian]>v) return n+1;
if(l==r) return l;
int mid=(l+r>>1);
if(x>mid) return right_query(bian<<1|1,mid+1,r,x,v);
int ans=right_query(bian<<1,l,mid,x,v);
if(ans!=n+1) return ans;
return right_query(bian<<1|1,mid+1,r,x,v);
}
}tr1;
struct node2{
int cnt;
int ls[N<<5],rs[N<<5],tr[N<<5],rt[N];
int build(int l,int r){
++cnt;
if(l==r) return cnt;
int tmp=cnt;
int mid=(l+r>>1);
ls[tmp]=build(l,mid);
rs[tmp]=build(mid+1,r);
return tmp;
}
int update(int rt2,int l,int r,int x,int v){
++cnt;
int tmp=cnt;
tr[cnt]=tr[rt2]+v;
ls[cnt]=ls[rt2];
rs[cnt]=rs[rt2];
if(l==r) return tmp;
int mid=(l+r>>1);
if(x<=mid) ls[tmp]=update(ls[rt2],l,mid,x,v);
else rs[tmp]=update(rs[rt2],mid+1,r,x,v);
return tmp;
}
void Build(){
int last=0;
for(auto i:cha){
if(!len||val[len]!=i.ban) val[++len]=i.ban;
rt[len]=update(last,1,n,i.wei,i.val);
last=rt[len];
}
}
int query(int bian,int l,int r,int L,int R){
if(!bian) return 0;
if(L<=l&&R>=r) return tr[bian];
int mid=(l+r>>1);
int sum=0;
if(L<=mid) sum+=query(ls[bian],l,mid,L,R);
if(R>mid) sum+=query(rs[bian],mid+1,r,L,R);
return sum;
}
}tr2;
inline int read(){
int t=0,f=1;
register char c=getchar();
while (c<48||c>57) f=(c=='-')?(-1):(f),c=getchar();
while (c>=48&&c<=57)t=(t<<1)+(t<<3)+(c^48),c=getchar();
return f*t;
}
bool cmp(int x,int y){
return a[x]<a[y];
}
bool cmp1(node x,node y){
if(x.ban!=y.ban) return x.ban<y.ban;
if(x.wei!=y.wei) return x.wei<y.wei;
return x.val<y.val;
}
signed main(){
n=read(),q=read();
for(int i=1;i<=n;i++) a[i]=read(),pai[i]=i;
sort(pai+1,pai+1+n,cmp);
tr1.build(1,1,n);
set<int> wei;
for(int i=1;i<=n;i++){
int x=pai[i],l=0,r=n+1;
cha.push_back((node){a[x]*2,x,1});
auto it=wei.lower_bound(x);
if(it!=wei.end()) r=*it;
if(it!=wei.begin()) l=*(--it);
if(l){
if(b[l]<a[x]*2){
cha.push_back((node){b[l],l,1});
cha.push_back((node){a[x]*2,l,-1});
}
b[l]=INT_MAX;
if(x-l>1) b[l]=a[x]+tr1.query(1,1,n,l+1,x-1);
}
b[x]=INT_MAX;
if(r!=n+1)
if(r-x>1) b[x]=a[x]+tr1.query(1,1,n,x+1,r-1);
wei.insert(x);
}
sort(cha.begin(),cha.end(),cmp1);
tr2.Build();
while(q--){
int L=read(),R=read(),k=read();
int minn=tr1.query(1,1,n,L,R)*2;
if(k==1){
cout<<minn<<"\n";
continue;
}
int l=minn,r=INT_MAX,mid;
while(l<r){
mid=(l+r>>1);
int pr=tr1.left_query(1,1,n,R,mid/2);
int pl=tr1.right_query(1,1,n,L,mid/2);
int x=upper_bound(val+1,val+1+len,mid)-val-1;
int res=tr2.query(tr2.rt[x],1,n,pl,pr-1)+1;
int minnn=INT_MAX;
if(L<pl) minnn=min(minnn,tr1.query(1,1,n,L,pl-1));
if(R>pr) minnn=min(minnn,tr1.query(1,1,n,pr+1,R));
if(minnn<INT_MAX&&minnn+max(a[pl],a[pr])<=mid) res++;
if(res>=k) r=mid;
else l=mid+1;
}
cout<<l<<"\n";
}
return 0;
}

浙公网安备 33010602011771号