【hihocoder#1384】Genius ACM(倍增+归并)

传送门


  首先我们要知道校验值如何最大。显而易见的,当校验值最大时,一定是(最大和最小的差的平方)+(次大和次小的平方)……。
  那么求解校验值就需要排序来辅助配对,那么我们可能会选择快排,我一开始也是这么想,但是这样是不行的(下文将解释)

  其次,题目要求在一段数列中分成最少几段使得每段的校验值最大值不超过T,那么因为校验值是几个差的和,所以如果多了一个数那么校验值的最大值一定会变大。

  所以我们可以把问题转化为:确定一个左端点L,尽量使得右端点R最长满足L~R校验值超过T。
  
  我们很容易想到可以二分这个右端点,然而这样也是不行的!为什么呢?因为T是全局的,假设T非常少,然而二分的上限非常大,那么我们在对一个左端点进行确定的时候可能需要进行很多次校验值运算,但可能右端点只往后移了一点点,这样就会非常耗时!

  那么这题既然无法用二分做,考虑倍增。
  定义一个p,初始值为1,作为拓展的长度。那么:
如果:( 校验值(L~R+p)<= T ) 那么:R=R+p,p*=2
否则:p=p/2
如果 p=0则说明无法在拓展,那么就找到了最远的右端点,同时段数++

  但是倍增所需要的时间是 O(logN) O ( l o g N ) ,求校验值排序的时间是 O(NlogN) O ( N l o g N ) ,所以总体时间为 O(Nlog2N) O ( N l o g 2 N ) ,超时了。。

  那么我们发现,快速排序每次排的区域中有一部分是已经有序了的,只是新加进来的无序,那么我们不需要花费大量时间来将已经有序的部分再排一遍。 那么我们就要用到归并排序的思想,首先快排新加进来的无序部分,然后把已经排好的部分合并进去,这就是归并排序的基本思想(把两个有序的集合合并成一个有序的集合),具体不再赘述,可以去看这篇博客

  注意!!!我的程序里有这样一句话:

if(r+p<=n && calc(l,r+1,r+p))

我一开始是这么打的:

if(calc(l,r+1,r+p) && r+p<=n )

这样就炸爆了。。
千万要注意,这种有设立边界的情况下一定要先判断边界,要不然他会先执行Calc函数而造成死循环或者访问无效空间。


code:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=510010;
int n,m; ll T;
ll s[N],tmp[N],tmp2[N];
bool calc(int l,int rr,int r)
{
    //l~rr  rr~r
    int t=l,len=r-l+1;
    for(int i=rr;i<=r;i++) 
        tmp[i]=s[i];
    sort(tmp+rr,tmp+r+1);
    if(l==rr) for(int i=l;i<=r;i++) tmp2[i]=tmp[i];
    else
    {
        int i=l,j=rr;
        while(i<rr && j<=r)
        {
            if(tmp[i]<=tmp[j]) tmp2[t++]=tmp[i++];
            else tmp2[t++]=tmp[j++];
        }
        while(i<rr) tmp2[t++]=tmp[i++];
        while(j<=r) tmp2[t++]=tmp[j++];
    }
    ll sum=0;
    for(int i=l;i<=min(l+len/2-1,l+m-1);i++)
    {
        sum+=(tmp2[i]-tmp2[r-i+l])*(tmp2[i]-tmp2[r-i+l]);
    }
    if(sum<=T) 
    {
        for(int i=l;i<=r;i++) tmp[i]=tmp2[i];
        return 1;
    }
    else return 0;

}
int main()
{
//  freopen("a.in","r",stdin);
//  freopen("a.out","w",stdout);
    int ti;scanf("%d",&ti);
    while(ti--)
    {

        scanf("%d%d%lld",&n,&m,&T);
        for(int i=1;i<=n;i++)
            scanf("%lld",&s[i]);
        memset(tmp,0,sizeof(tmp));
        memset(tmp2,0,sizeof(tmp2));
        int p=1,l=1,r=1,ans=0;
        tmp[1]=s[1];
        while(r<n)
        {
            if(!p)
            {
                ans++; p=1;
                r++;   l=r;
                tmp[l]=s[l];
                continue;
            }
            if(p)
            {
                if(r+p<=n && calc(l,r+1,r+p))//这里一定要先判断再运算,要不然会先执行运算然后炸掉 
                {
                    r+=p,p*=2;
                    if(r==n) break;
                }
                else p/=2;
            }
        } 
        if(r==n) ans++; 
        printf("%d\n",ans);
    } 
    return 0;
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值