2019ACM-ICPC网络赛 沈阳 F题 二分的灵活运用

本文详细解析了2019年沈阳ICPC网络赛F题,通过灵活运用二分法解决池塘水量平衡问题。探讨了如何通过二分查找确定在特定操作次数下池塘间水量差值,强调了寻找单调关系的重要性。

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

2019沈阳站ICPC网络赛 F题 二分的灵活运用

2019沈阳ICPC网络赛 F题题面

题意
有n个池塘,第i个池塘里初始状态有a[i]个单位的水,每天都进行一次操作:把水最多的池塘里(如果有多个随机取一个)的一个单位的水搬到水最少的池塘里(如果有多个随机取一个),问k次之后的水最多的池塘的水量减去水最少的池塘的水量是多少个单位?
限制:1000ms 262144K

思路
通常情况下二分的对象要关于筛选结果的区间具有某种单调性质,并且具有对于某一个结果判断是否符合题意的方法,这里的二分同样具有以上性质,只是运用比较灵活,具体如下:
我们的目标是求出k次之后的最大值(和最小值),假设k次操作之后,最大值为mid(最大值和最小值的筛选是一样的,这里以最大值为例)
1)所有初始状态下比mid大的数字a[i],都要用a[i]-mid天来达到现在假设的mid最大值的状态,令对其求和为sum
2)如果sum >k,说明k次不够用,不够让mid成为最大值的次数,只能向小筛,如果sum <k,说明k次够用,但说不定剩下的次数够让跟小的mid成为最大值,这就要求我们求出sum<k时的最小的mid
经过讨论我们明显可以看到:具体的单调关系是:让mid更小,需要更多次,sum更大,而且对于一个k,只有一个确定的最大值

反思
难点在于自己寻找变量或者方法,来确立抽象的单调关系,或者对单调关系不敏感,需要总结思考的经验

注意事项
1)是对初始状态a[i]之中的最小值到最大值之间的所有整数进行二分,而不是对所有的a[i]进行二分
2)一开始就要先对平均值avg(整除,算小于avg的部分是不需要讨论整除性问题的)算出sum与k进行比较,如果sum>k,继而二分最大最小值,如果sum<=k,能整除就是0,不能整除就是1
3)建议二分最大最小值的两个函数不要复制粘贴,要仔细考虑每一个大于号或者小于号以及递归调用和所用变量的含义,直接复制容易少改或改错
4)如果不易理解可以画n条线段表示各个水塘的水量,模拟过程以便理解

AC代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 5e6;
typedef long long ll;
int n,k;
ll a[maxn];
ll sum = 0;
int ans = 0,ans1 = 0;
void erfen(int l,int r)
{
    sum = 0;
    int mid = (l+r)/2;
    if(r == l) {ans = r;return;}
    if(r-l == 1)
    {
        mid = l;
        for(int i = 0;i < n;i++)
            if(a[i]>mid) sum+=a[i]-mid;
        if(sum > k) ans = r;
        else ans = l;
        return;
    }
    for(int i = 0;i < n;i++)
        if(a[i]>mid) sum+=a[i]-mid;
    if(sum > k)erfen(mid,r);
    else if(sum == k) {ans = mid;return;}
    else erfen(l,mid);
    return;
}

void erfen2(int l,int r)
{
    sum = 0;
    int mid = (l+r)/2;
    if(r-l == 1)
    {
        mid = r;
        for(int i = 0;i < n;i++)
            if(a[i]<mid) sum+=mid-a[i];
        if(sum <= k) ans1 = r;
        else ans1 = l;
        return;
    }
    if(r == l) {ans1 = r;return;}
    for(int i = 0;i < n;i++)
        if(a[i]<mid) sum+=mid-a[i];
    if(sum > k)erfen2(l,mid);
    else if(sum == k) {ans1 = mid;return;}
    else erfen2(mid,r);
    return;
}
int main()
{
    int mh,md,mz;
    while(~scanf("%d%d",&n,&k)){
        ll sum1 = 0;
        for(int i = 0;i < n;i++)
        {
            scanf("%lld",&a[i]);
            sum1 += a[i];
        }
        sort(a,a+n);
        mh = sum1/n; md = sum1 %n;
        if(md) mz = mh+1;
        else mz = mh;
        sum1 = 0;
        for(int i = 0;i < n;i++)
        {
            if(a[i] < mh) sum1 += mh-a[i];
            else break;
        }
        if(k > sum1)
        {ans = md? 1:0;printf("%d\n",ans);}
        else
        {
            erfen(mh,a[n-1]);erfen2(a[0],mh);
            printf("%d\n",ans-ans1);
        }


    }
    return 0;
}

PS:如果读者有建议或者没有理解的地方,欢迎评论或指正,这是我第一次做题解,还有很多需要完善的地方,劳驾各位读者提出希望改进的细节,谢谢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值