Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 260|回复: 0

[默认分类] hiho 修补木桶(二分)

[复制链接]
  • TA的每日心情
    开心
    2021-12-13 21:45
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2018-3-15 11:19:58 | 显示全部楼层 |阅读模式
    时间限制: 10000ms
    单点时限: 1000ms
    内存限制: 256MB

    描述
    一只木桶能盛多少水,并不取决于桶壁上最高的那块木板,而恰恰取决于桶壁上最短的那块。
    已知一个木桶的桶壁由N块木板组成,第i块木板的长度为Ai。
    现在小Hi有一个快捷修补工具,每次可以使用修补工具将连续的不超过L块木板提高至任意高度。
    已知修补工具一共可以使用M次(M*L<N),如何修补才能使最短的那块木板最高呢?
    注意: 木板是环形排列的,第N-1块、第N块和第1块也被视为连续的。
    输入
    第1行:3个正整数,N, M, L。分别表示木板数量,修补工具使用次数,修补工具每次可以同时修补的木板数。 1≤N≤1,000,1≤L≤20,M*L<N
    第2行:N个正整数,依次表示每一块木板的高度Ai,1≤Ai≤100,000,000
    输出
    第1行:1个整数。表示使用修补工具后,最短木块的所能达到的最高高度
    样例说明
    第一个修补工具覆盖[2 3 4]
    第二个修补工具覆盖[5 8 1]


    样例输入8 2 38 1 9 2 3 4 7 5样例输出
    7

    《修补木桶》题目分析

    本题可以使用二分答案的思路解决。

    我们考虑这样一个问题,假设最终最短的木板长度至少是K,最小需要使用修复工具几次? 为了描述方便我们将这个最少次数记作F(K)。

    于是我们的问题变成求出最大的K,满足F(K) <= M。

    如果我们将F(K)看成一个函数,随着K增加,我们要修复的木板越来越多,显然F(K)也会越来越大。

    换句话说F(K)是单调递增的。我们可以用二分来求出最大的K。

    考虑 1 <= Ai <= 100000000,答案也一定在[1, 100000000]之间。在这个范围内二分的复杂度是O(log(Max{Ai}))。

    然后我们的问题是对于确定的K,计算F(K)。

    当K确定是,我们就可以确定哪些木板需要被修复(Ai < K的木板)。

    由于木桶是环形的,我们需要枚举起点,复杂度O(N)。

    一旦起点确定,就可以贪心求出每一次修复的位置。从而计算出F(K),复杂度O(N)。

    于是总复杂度是O(log(Max{Ai})N^2)

    这个算法可以通过所有的数据。

    不过上面算法中二分和计算F(K)都有优化的空间。

    对于二分答案这部分,实际上答案一定是某个Ai,所以我们可以优化到O(logN)的二分。

    对于计算F(K)的部分,考虑到修复范围是L,所以O(N)的枚举起点可以优化到O(L)。


    #include<cstdio>#include<iostream>#include<algorithm>#include<cstring>using namespace std;int n,m,l,a[2*1005],b[1005],rr,ll,mid;bool is_ok() {        if(b[0]==mid) return true;        //时间复杂度为l*n        int cnt=0;//需要的段数        int cnt_start=0;//已开始的起点数        bool f1=false;        for(int i=0; i<n; ++i) {                int cnt=1,j,last;                if(a<mid) {                        last=i+n;                        j=i+l;                        ++cnt_start;                } else continue;                while(j<last) {                        if(a[j]<mid) {                                ++cnt;                                j+=l;                        } else ++j;                }                if(cnt<=m) {                        f1=true;                        break;                }                if(cnt_start>=l) break;        }        return f1;}int main() {        scanf("%d%d%d",&n,&m,&l);        for(int i=0; i<n; ++i)                scanf("%d",a+i);        memcpy(b,a,n*sizeof(int));        memcpy(a+n,a,n*sizeof(int));        sort(b,b+n,less<int>());        ll=0,rr=n-1;        while(ll<rr) {                int t=(ll+rr+1)/2;//这个+1要注意                mid=b[t];                if(is_ok()) {                        ll=t;                } else {                        rr=t-1;//这个地方要注意下                }        }        cout<<b[ll]<<endl;        return 0;}

    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-16 19:21 , Processed in 0.395949 second(s), 34 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表