题解:Towering Arrays

题目传送门

VP 场切。

题意分析

二分答案

显然,条件 \(b_j\geq p-\vert i-j\vert\) 成立的 \(p\) 具有单调性。即若 \(p=p_0\) 时该条件成立,则 \(p<p_0\) 时也成立。

因此可以二分答案 \(p\),取最大值即可。

线段树维护

\(p\) 已经确定,那么便是判断条件。显然绝对值并不好处理,考虑拆开。

即需要满足条件:

\[\begin{cases} b_j+i-j\geq p&j<i\\ b_j\geq p &j=i\\ b_j-i+j\geq p&j>i \end{cases} \]

容易发现 \(j<i\) 的部分和 \(i<j\) 的部分无关,因此考虑分开处理。对于每一个 \(i\),包含 \(a_i\)\(b\) 一定是左边一段,右边一段。两边可以分别求解一个最长的满足条件的子序列。如果这两个子序列加上 \(a_i\) 本身合起来,长度大于等于 \(n-k\),则说明这是一种合法的解

问题就来到如何求解子序列。

先考虑左边的,设 \(\textit{dp}_i\) 表示 \(a_1,a_2,\cdots,a_i\) 中最长的合法子序列,且钦定此处的 \(a_i\) 为上文限制条件中的 \(i\)

为了与代码保持一致,记 \(\textit{mid}\) 表示当前二分得到的 \(p\)

线段树维护,初始时节点 \([i,i]\) 的值为 \(\textit{mid}-i+1\)

求解 \(\textit{dp}_i\) 时:

  1. 在线段树上二分,找到 \(p\) 表示最左侧的小于等于 \(\min(a_i,\textit{mid})\) 的位置。特别地,若不存在则记为 \(+\infty\)
  2. 将线段树上的 \([p,n]\) 都增加 \(1\),表示 \(\textit{dp}_i\) 能够递推到这些位置。
  3. 此时:
    • \(a_i<\textit{mid}\),表明 \(i\) 不可能是上文限制条件中的 \(i\)\(\textit{dp}_i\leftarrow-\infty\)。(其实任意值都可以。)
    • \(a_i\geq\textit{mid}\),则 \(\textit{dp}_i\) 即最右侧的最大值为 \(\textit{mid}+1\) 的位置。特别地,若不存在则记为 \(-\infty\)

反向再做一遍即可。最后判断相加是否大于等于 \(n-k\) 即可。

AC 代码

//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=2e5,K=2e5,V=1e9,inf=0x3f3f3f3f;
int n,k,a[N+1];
int Mid;
struct segTree{
	struct node{
		int l,r;
		int tag,max,min;
	}t[N<<2|1];
	void up(int p){
		t[p].max=max(t[p<<1].max,t[p<<1|1].max);
		t[p].min=min(t[p<<1].min,t[p<<1|1].min);
	}
	void build(int p,int l,int r){
		t[p]={l,r};
		if(l==r){
			t[p].max=t[p].min=Mid-l+1;
			return;
		}
		int mid=l+r>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		up(p);
	}
	void down(int p){
		if(t[p].tag){
			t[p<<1].max+=t[p].tag;
			t[p<<1].min+=t[p].tag;
			t[p<<1].tag+=t[p].tag;
			t[p<<1|1].max+=t[p].tag;
			t[p<<1|1].min+=t[p].tag;
			t[p<<1|1].tag+=t[p].tag;
			t[p].tag=0;
		}
	}
	void add(int p,int l,int r,int k){
		if(l<=t[p].l&&t[p].r<=r){
			t[p].max+=k;
			t[p].min+=k;
			t[p].tag+=k;
			return;
		}
		down(p);
		if(l<=t[p<<1].r){
			add(p<<1,l,r,k);
		}
		if(t[p<<1|1].l<=r){
			add(p<<1|1,l,r,k);
		}
		up(p); 
	}
	int findL(int p,int x){
		if(t[p].l==t[p].r){
			if(t[p].min<=x){
				return t[p].l;
			}else{
				return inf;
			}
		}
		down(p);
		if(t[p<<1].min<=x){
			return findL(p<<1,x);
		}else{
			return findL(p<<1|1,x);
		}
	}
	int findR(int p){
		if(t[p].l==t[p].r){
			if(t[p].max==Mid+1){ 
				return t[p].l;
			}else{
				return -inf;
			}
		}
		down(p);
		if(t[p<<1|1].max==Mid+1){
			return findR(p<<1|1);
		}else{
			return findR(p<<1);
		}
	}
}t;
bool check(int mid){
	Mid=mid;
	t.build(1,1,n);
	static int dp[N+1];
	for(int i=1;i<=n;i++){
		int p=t.findL(1,min(a[i],mid));
		if(p<=n){
			t.add(1,p,n,1);
		}
		if(a[i]>=mid){
			dp[i]=t.findR(1);
		}
	}
	t.build(1,1,n);
	for(int i=n;i>=1;i--){
		int p=t.findL(1,min(a[i],mid));
		if(p<=n){
			t.add(1,p,n,1);
		}
		if(a[i]>=mid && t.findR(1)+dp[i]>=n-k){
			return true;
		}
	}
	return false;
}
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	int T;
	cin>>T;
	while(T--){
		cin>>n>>k;
		for(int i=1;i<=n;i++){
			cin>>a[i];
		}
		int l=0,r=V;
		while(l<r){
			int mid=l+r+1>>1;
			if(check(mid)){
				l=mid;
			}else{
				r=mid-1;
			}
		}
		cout<<l<<'\n';
	}
	
	cout.flush();
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}
posted @ 2025-08-21 00:26  TH911  阅读(10)  评论(0)    收藏  举报