P10059 Choose

by Ecrade_

结论: 记长度为 LL 时对应的 XX 最大值为 f(L)f(L),则 f(L)f(L) 单调不降。


证明:

对于任意长度 2Lnk+12\le L\le n-k+1,记 aa 所有长度为 LL 的子序列的极差依次为 p1,p2,,pnL+1p_1,p_2,\dots,p_{n-L+1},记 pp 从大到小排序后所的序列为 p1,p2,,pnL+1p'_1,p'_2,\dots,p'_{n-L+1},则 f(L)=pkf(L)=p'_k;同样地,记 aa 所有长度为 L1L-1 的子序列的极差依次为 q1,q2,,qnL+2q_1,q_2,\dots,q_{n-L+2},记 qq 从大到小排序后所的序列为 q1,q2,,qnL+2q'_1,q'_2,\dots,q'_{n-L+2},则 f(L1)=qkf(L-1)=q'_k

故我们仅需证明 pkqkp'_k\ge q'_k 即可。而不难发现,1inL+1\forall 1\le i\le n-L+1piqip_i\ge q_ipiqi+1p_i\ge q_{i+1},由此可推出 pp 中至少有 kk 个数不小于 qkq'_k,进一步推出 pkqkp'_k\ge q'_k

故命题 pkqkp'_k\ge q'_k 成立,即 f(L)f(L) 单调不降。


XX 最大值即为 f(nk+1)f(n-k+1),极差可用单调队列求得。LL 最小值有两种方法求。

法一

直接上二分,具体实现见代码。

时间复杂度 O(Tnlogn)O(Tn\log n)

#include<bits/stdc++.h>
using namespace std;
int t,n,k,a[100009];
int qs[100009],qb[100009];
inline int read(){
	int s = 0,w = 1;
	char ch = getchar();
	while (ch > '9' || ch < '0'){ if (ch == '-') w = -1; ch = getchar();}
	while (ch <= '9' && ch >= '0') s = (s << 1) + (s << 3) + (ch ^ 48),ch = getchar();
	return s * w;
}
int work(int x,int lim,bool opt){
	int res = 2e9,cnt = 0,hs = 1,ts = 0,hb = 1,tb = 0;
	for (int i = 1;i <= n;i += 1){
		while (ts >= hs && i - qs[hs] + 1 > x) hs += 1;
		while (tb >= hb && i - qb[hb] + 1 > x) hb += 1;
		while (ts >= hs && a[i] < a[qs[ts]]) ts -= 1;
		while (tb >= hb && a[i] > a[qb[tb]]) tb -= 1;
		qs[++ ts] = i,qb[++ tb] = i;
		if (opt == 0 && i >= x) res = min(res,a[qb[hb]] - a[qs[hs]]);
		if (opt == 1 && i >= x && a[qb[hb]] - a[qs[hs]] >= lim) cnt += 1;
		if (opt == 1 && cnt >= k) break;
	}
	if (opt == 0) return res;
	if (cnt < k) return 0;
	return 1;
}
int main(){
	t = read();
	while (t --){
		n = read(),k = read();
		for (int i = 1;i <= n;i += 1) a[i] = read();
		int xans = work(n - k + 1,0,0),lans = n - k + 1,l = 1,r = n - k + 1;
		while (l <= r){
			int mid = (l + r) >> 1;
			if (work(mid,xans,1)) lans = mid,r = mid - 1;
			else l = mid + 1;
		}
		printf("%d %d\n",xans,lans);
	}
	return 0;
}

法二

考虑对于每个长度 LL,求出极差不小于 XX 最大值的区间个数 cLc_L

从小到大枚举 ii,求出以 ii 为左端点的极差不小于 XX 最大值的极短连续区间,记该区间右端点为 rir_i,则对所有 rii+1Lni+1r_i-i+1\le L\le n-i+1 都执行 cLcL+1c_L\gets c_L+1

具体实现上,由于所有极短连续区间均无包含关系,故可使用单调队列 + 双指针求得;由于每次执行 cLcL+1c_L\gets c_L+1LL 都是一段连续区间,故可用前缀和记录。

时间复杂度 O(Tn)O(Tn)

#include<bits/stdc++.h>
using namespace std;
int t,n,k,a[100009],cnt[100009];
int qs[100009],qb[100009];
inline int read(){
	int s = 0,w = 1;
	char ch = getchar();
	while (ch > '9' || ch < '0'){ if (ch == '-') w = -1; ch = getchar();}
	while (ch <= '9' && ch >= '0') s = (s << 1) + (s << 3) + (ch ^ 48),ch = getchar();
	return s * w;
}
int main(){
	t = read();
	while (t --){
		n = read(),k = read();
		for (int i = 1;i <= n;i += 1) a[i] = read(),cnt[i] = 0;
		int xans = 2e9,lans = 0,hs = 1,ts = 0,hb = 1,tb = 0,r = 0,all = 0;
		for (int i = 1;i <= n;i += 1){
			while (ts >= hs && i - qs[hs] > n - k) hs += 1;
			while (tb >= hb && i - qb[hb] > n - k) hb += 1;
			while (ts >= hs && a[i] < a[qs[ts]]) ts -= 1;
			while (tb >= hb && a[i] > a[qb[tb]]) tb -= 1;
			qs[++ ts] = i,qb[++ tb] = i;
			if (i >= n - k + 1) xans = min(xans,a[qb[hb]] - a[qs[hs]]);
		}
		hs = 1,ts = 0,hb = 1,tb = 0;
		for (int i = 1;i <= n;i += 1){
			while (ts >= hs && qs[hs] < i) hs += 1;
			while (tb >= hb && qb[hb] < i) hb += 1;
			while (r < n && (ts < hs || tb < hb || a[qb[hb]] - a[qs[hs]] < xans)){
				r += 1;
				while (ts >= hs && a[r] < a[qs[ts]]) ts -= 1;
				while (tb >= hb && a[r] > a[qb[tb]]) tb -= 1;
				qs[++ ts] = r,qb[++ tb] = r;
			}
			if (a[qb[hb]] - a[qs[hs]] < xans) break;
			cnt[r - i + 1] += 1,cnt[n - i + 2] -= 1;
		}
		for (int i = 1;i <= n;i += 1) cnt[i] += cnt[i - 1];
		while (cnt[lans] < k) lans += 1;
		printf("%d %d\n",xans,lans);
	}
	return 0;
}

by _JF_

沿袭上面的做法,其实验题人在二分完 LL 后采用数据结构维护区间最值来进行检验是否合法。(可能好像写,好像赛时大部分人也是这么写的)

这样做的复杂度是 O(Tnlog2n)O(Tn\log^2 n) 的,没有卡掉,也可以通过。

#include<bits/stdc++.h>
using namespace std;
const int N =1e5+10;
int F[N][20],f[N][20],dis[N],a[N],Log[N],n,k;
inline int read()
{
    register int x=0,f=0;
    register char t=getchar();
    while(t<'0'||t>'9')f^=(t=='-'),t=getchar();
    while(t>='0'&&t<='9')x=(x<<3)+(x<<1)+(t^48),t=getchar();
    return f?-x:x;
}
bool cmp(int a,int b){
	return a>b;
}
int QueryMax(int l,int r){
	int k=Log[r-l+1];
	return max(f[l][k],f[r-(1<<k)+1][k]);
}
int QueryMin(int l,int r){
	int k=Log[r-l+1];
//	cout<<l<<" "<<r<<" "<<k<<" "<<F[l][k]<<endl;
	return min(F[l][k],F[r-(1<<k)+1][k]);
}
void init(){
	memset(f,-0x3f,sizeof(f)),memset(F,0x3f,sizeof(F));
// 	for(int j=0;j<=Log[n];j++)
// 		for(int i=0;i+(1<<j)-1<=n;i++)
// 			F[i][j]=INT_MAX,f[i][j]=-INT_MAX;
	for(int i=1;i<=n;i++)
		F[i][0]=a[i],f[i][0]=a[i];
	for(int j=1;j<=Log[n];j++)
		for(int i=1;i+(1<<j)-1<=n;i++){
			f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
			F[i][j]=min(F[i][j-1],F[i+(1<<(j-1))][j-1]);
		}	
}
int check(int len){
	int p=0;
	for(int i=1;i+len-1<=n;i++){
		int maxx=QueryMax(i,i+len-1),minn=QueryMin(i,i+len-1);
		dis[++p]=maxx-minn;
//		cout<<i<<" "<<i+len-1<<" "<<maxx<<" "<<minn<<endl;
	}
	if(p<k)	return -1;
	sort(dis+1,dis+p+1,cmp);
	return dis[k];
}
void Find(int pre){
	int l=1,r=n,AnsL=INT_MAX;
	while(l<=r){
		int mid=(l+r)>>1,now=check(mid);
		if(now==-1)	r=mid-1;
		else if(now<pre)	l=mid+1;
		else if(now==pre)	AnsL=min(AnsL,mid),r=mid-1;
//		cout<<Ans<<" "<<AnsL<<endl;
	}
	cout<<pre<<" "<<AnsL<<endl;
} 
int main(){
	for(int i=2;i<=1e5;i++)	Log[i]=Log[i>>1]+1;
	int t;
	t=read();
	while(t--){
		n=read(),k=read();
		for(int i=1;i<=n;i++)	a[i]=read();
		init(); 
//		cout<<check(3)<<endl;
		Find(check(n-k+1));	
	}
	return 0;
}
posted @   June_Failure  阅读(7)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示