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;
}
posted @ 2025-03-27 10:05  ask_silently  阅读(111)  评论(0)    收藏  举报