倍增1 天才ACM

该博客讨论了一个算法问题,涉及校验值的计算和如何有效地将一个数列分成若干段,使得每段的校验值不超过给定阈值T。通过排序和动态调整区间长度,实现了O(Nlog2N)的时间复杂度,解决了暴力遍历导致的时间超限问题。

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

在这里插入图片描述
将该问题分解:
问题1:校验值应该如何取
问题2:如何把A分成若干段使得每一段的校验值都不超过T

问题一求校验值:
写一下公式(假设有k个数):
(a1-a2)2+(a3-a4)2+···+(ak-1-ak)2
=a12+a22+···+ak2-2*(a1a2+a3a4+···+ak-1ak)
由基本不等式可以知道,每对数中的两个数需要相差尽可能大,即我们只需要对s进行排序,最小和最大,次小和次大依次配对,奇数情况舍弃中间点即可
问题二分段:
我们要把A分成若干段,且每一段的校验值不超过T,求最少分成几段。即在我们确定左端点之后,满足A[L]~A[R]的校验值不超过T的前提下,R最大取多少。
如果我们直接暴力遍历,时间复杂度是O(N*NlogN)其中N是右端点会遍历所有位置,NlogN是对当前区间进行排序。会超时
考虑倍增,确定了左端点后,我们倍增地去找右端点。
假设当前的左端点是L,右端点是R,区间长度是len。
如果该区间满足条件,我们就让R+=len,len<<=1,
如果该区间不满足条件,就让len>>=1。
这样右端点只会走logN次,加上排序就是O(Nlog2N)
代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e5+10;
int n,m,k;
ll t;
ll a[N];
ll calc(int l,int r){
    static ll c[N];
    int len=r-l+1;
    for(int i=l;i<=r;i++)c[i-l+1]=a[i];
    sort(c+1,c+1+len);
    ll res=0;
    for(int i=1;i<=len/2&&i<=m;i++)res+=(c[len-i+1]-c[i])*(c[len-i+1]-c[i]);
    return res;
}
void solve(){
    scanf("%d%d%lld",&n,&m,&t);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    int ans=0;
    int L=1,R;
    while(L<=n){
        //cout<<L<<endl;
        R=L+1;
        int len=1;
        while(len){
            if(R+len-1<=n&&calc(L,R+len-1)<=t){
                R+=len;
                len<<=1;
            }
            else len>>=1;
        }
        ans++;
        L=R;
    }
    printf("%d\n",ans);
}
int main(){
    scanf("%d",&k);
    while(k--)solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值