Codeforces Round 890 (Div. 2) C. To Become Max(二分 补写法 二分套二分)

博客围绕一个数组操作问题展开,给定长度为n的数组,一次操作可对满足条件的下标元素加一,求最多操作k次时数组的最大值。先介绍n=1e3时对每个值二分最大答案的常规做法,又给出n=1e5的解法,包括构造新序列、用ST表预处理及二分最终答案等步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

给定一个长度为n(n<=1e3)的数组,

在一次操作里,你可以选择一个满足a[i]<=a[i+1]的下标i(1<=i<=n-1),对a[i]加一

问,你最多操作k次的情况下,数组的最大值是多少,输出最大值

思路来源

Submission #217368352 - Codeforces

题解

这题是n=1e3的,正常的写法O(n^2logv)的写法很多,对于每个值,二分其最大答案

比如我赛中的提交,Submission #217328890 - Codeforces

Bonus

这里给出一种n=1e5的做法,O(nlognlogv)

Hint

以样例里最具代表性的case为例,

n=5,k=6

6 5 4 1 5

可以令a[4]+3,然后令a[3]+1,令a[2]+1,令a[1]+1,最后答案是7

最终序列是形如7 6 5 4 5,记做A数组,

而7 6 5 4是我们取得的一段,记做A[l,r],

显然,需要满足,

①:A[r]>=a[r+1]-1

即所取区间的最后一个值,大于等于初始序列下一个位置的值减1,不然无法拔高a[r]

②:此外,A序列最终答案[l,r]还一定是一段连续的、后项=前项-1的区间,

所以,这启发我们先给a[i]用初值拉齐,构造b[i]=a[i]-(n-i)

a: 6 5 4 1 5

b: 1 1 1 -1 4

终态数组

这里用A、B,和初始a、b区分

A: 7 6 5 4 5

B: 2 2 2 2 4

最终我们取的B区间是[2,2,2,2],

观察①限制,发现在新数组中,等价于B[r]<=b[r+1]

贪心最小代价时,需要找到[i,n]里满足>=B[r]的最左位置

而2就是最终的取值v(v=B[l]=...=B[r]),被还原回去,w=v+(n-i),也就是7

Solution

1. 根据a序列,构造新序列b[i]=a[i]-(n-i)

2. ST表预处理出b[i]的区间最大值,预处理b[i]的前缀和

3. 二分最终答案w,w在B序列里考虑时,B[i]=w-(n-i)

4. 通过ST表+二分,找到满足b[j]>=B[i]的最小的j(j>=i),

也就意味着,[i,j)这一段都是<B[i]的,都需要被拔高,利用前缀和,判断k次机会,是否可满足

代码

// #include<bits/stdc++.h>
#include<iostream>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair<ll,ll> P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
const int N=1e5+10,M=20,INF=0x3f3f3f3f;
int t,n,k,a[N],b[N],mx[N][M],lg[N];
ll sum[N];
void init(){
    for(int i=2;i<N;++i){
        lg[i]=lg[i>>1]+1;
    }
}
//最大值的最左位置
void ST(int n){
    for(int i=1;i<=n;++i)mx[i][0]=b[i];
	for(int len=1;(1<<len)<=n;++len){
		for(int i=1;i+(1<<len)-1<=n;++i){
			mx[i][len]=max(mx[i][len-1],mx[i+(1<<(len-1))][len-1]);
		}
	}
}
int RMQ(int l,int r){
	int k=lg[r-l+1];
    return max(mx[l][k],mx[r-(1<<k)+1][k]);
}
ll cal(int l,int r){
    return sum[r]-sum[l-1];
}
bool ok(int v){
    rep(i,1,n-1){
        int w=v-(n-i),p=RMQ(i+1,n);//[2,2,2,>=2] [i,p] b[l]=b[l+1]=...=b[p-1]<=b[p] [i,p-1)拔高,p不变
        if(p<w)continue;
        int l=i+1,r=n;//>=w的最左位置
        while(l<=r){
            int mid=(l+r)/2;
            if(RMQ(i+1,mid)>=w)r=mid-1;
            else l=mid+1;
        }
        if(cal(i,l-1)+k>=1ll*(l-i)*w){
            return 1;
        }
    }
    return 0;
}
int sol(){
	int ans=0;
	rep(i,1,n){
        ans=max(ans,a[i]);
        b[i]=a[i]-(n-i);
        sum[i]=sum[i-1]+b[i];
    }
    ST(n);
    int l=ans+1,r=ans+k;
    while(l<=r){
        int mid=(l+r)/2;
        if(ok(mid))l=mid+1;
        else r=mid-1;
    }
	return r;
}
int main(){
    init();
	sci(t);
	rep(j,1,t){
		sci(n),sci(k);
		rep(i,1,n)sci(a[i]);
        pte(sol());
	}
	return 0;
}
/*
3
31 1
99999997 99999997 99999997 99999997 99999997 99999999 100000000 99999999 100000000 100000000 99999998 99999999 100000000 99999997 99999998 99999998 99999999 100000000 99999998 100000000 99999998 100000000 99999997 99999997 99999997 99999998 100000000 99999997 99999998 99999997 100000000
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值