2018百度之星程序设计大赛初赛B——1004p1m2

HDU-6383

题目:

度度熊很喜欢数组!!

我们称一个整数数组为稳定的,若且唯若其同时符合以下两个条件:

1. 数组里面的元素都是非负整数。
2. 数组里面最大的元素跟最小的元素的差值不超过 1。

举例而言,[1,2,1,2] 是稳定的,而 [−1,0,−1] 跟 [1,2,3] 都不是。

现在,定义一个在整数数组进行的操作:

* 选择数组中两个不同的元素 a 以及 b,将 a 减去 2,以及将 b 加上 1。

举例而言,[1,2,3] 经过一次操作后,有可能变为 [−1,2,4] 或 [2,2,1]。

现在给定一个整数数组,在任意进行操作后,请问在所有可能达到的稳定数组中,拥有最大的『数组中的最小值』的那些数组,此值是多少呢?

 

 

Input

输入的第一行有一个正整数 T,代表接下来有几组测试数据。

对于每组测试数据:
第一行有一个正整数 N。
接下来的一行有 N 个非负整数 xi,代表给定的数组。

* 1≤N≤3×105
* 0≤xi≤108
* 1≤T≤18
* 至多 1 组测试数据中的 N>30000

 

 

Output

对于每一组测试数据,请依序各自在一行内输出一个整数,代表可能到达的平衡状态中最大的『数组中的最小值』,如果无法达成平衡状态,则输出 −1。

 

 

Sample Input

 

2 3 1 2 4 2 0 100000000

 

 

Sample Output

 

2 33333333

 

 

A为原数列,A*为由A变化成的稳定数列,那么题目所求的就是 所有A*中的最小值最大的一个,把这个值记为ans

首先可以先考虑能不能直接暴力枚举所有A*,然后求解...... 很明显是不符合实际的。

换种想法,能不能让ans从无穷大遍历到0,暴力枚举所有ans,然后判断这个ans是否可以构成A*。

那么要先解决判断的问题。

题目要求的是:* 选择数组中两个不同的元素 a 以及 b,将 a 减去 2,以及将 b 加上 1。(这里“不同”指的是下标不同)

也就是进行一次减法(-2),必须要进行一次加法(+1),两种操作的次数要相等。那么可以分开操作,然后再来看两种操作的次数。

我们先对原数列A中所有小于ans的数A[i],做加法,num_p += ans - A[i]

然后对原数列A中所有大于ans+1的数A[i],做减法,num_m += (A[i] - ans) / 2

这里可能会出现A[i] - ans 是奇数的情况,如:(A[i] - ans) / 2 = 3 / 2 = 1,那么其实这时候,A[i]做完减法后得到的数应该是ans+1,它可以作为这个数列A*的最大值,使得数列A*仍然满足 “稳定” 的条件。(min:ans,max:ans+1)

接下来要对num_p(进行加法的次数)和 num_m(进行减法的次数)做比较:

num_p = num_m 的情况:说明这个ans可以作为稳定数列A*的最小值。
num_p != num_m 的情况:

首先要了解的一点是:

对于一个数列A,如果它的最小值为min(A),那么它可以通过题目所述的两种操作,来得到一个稳定数组A*,其最小值仍为min(A)。具体做法是:我们更新数组A,将A中最小值+1,最大值-2,不断重复这个过程。由于减掉的比加上的多,最终数组A中的最大最小值会不断地相互逼近,直到最小值为min(A)。

那么对于num_p < num_m:此时当加法减法配对后,仍有一些数需要进行减法操作,也就是说,此时的数列A最小值已经是ans了,存在一些数大于ans+1,那么根据上面的结论,这个数列A是可以得到稳定数组的,所以这个ans合法。

对于num_p > num_m :此时当加法减法配对后,仍有一些数需要进行加法操作,由于我们进行的是-2,+1,也就是总的来说肯定是朝减少的方向进行,既然出现还要加上的操作,只能说明这个ans还太大了,满足不了,得不到稳定数组。

 

解决完判断的问题,剩下的就是对ans的范围从原先的 0~∞ 进一步缩减。

很明显的上界应当为数列A的最大值max(A):倘若大于max(A)的话,那么A中所有的数都要往上加才行,由于有减法操作,实现不了;

下界应当为数列A的最小值min(A):在前面红色的结论已经说明了,ans至少会等于min(A)。

那么可以看出采用的算法应该是二分法,边界:min(A)~ max(A)

代码:

//
//  main.cpp
//  初赛B1004
//
//  Created by jinyu on 2018/8/13.
//  Copyright © 2018年 jinyu. All rights reserved.
//

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long  LL;
const int MAXN = 3e5+7;
const long long INF = 0x3f3f3f3f3f3f;

long long A[MAXN];
int N;

bool judge(LL ans){
    LL num_p = 0;
    LL num_m = 0;
    for(int i = 0;i<N;++i){
        if(ans > A[i])
            num_p += ans-A[i];
        else if(ans+1 < A[i])
            num_m += (A[i]-ans)/2;
    }
    return num_m>=num_p;
}

int main(){
    
    int T;
    scanf("%d",&T);
    while (T--) {
        scanf("%d",&N);
        LL R = -1;
        LL L = INF;
        for(int i = 0;i<N;++i)
            scanf("%lld",&A[i]);
        
        sort(A,A+N);
        L = A[0];
        R = A[N-1];
        LL ans = -1;
        
        while(L<=R){
            
            LL mid = (L+R)/2;
            
            if(judge(mid)){
                L = mid + 1;
                ans = mid;
            }
            else
                R = mid - 1;
        }
        
        printf("%lld\n",ans);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值