zoukankan      html  css  js  c++  java
  • 刷题笔记-二分法

    数的范围

    给定一个按照升序排列的长度为n的整数数组,以及 q 个查询。
    对于每个查询,返回一个元素k的起始位置和终止位置(位置从0开始计数)。
    如果数组中不存在该元素,则返回“-1 -1”。

    输入格式

    第一行包含整数n和q,表示数组长度和询问个数。
    第二行包含n个整数(均在1~10000范围内),表示完整数组。
    接下来q行,每行包含一个整数k,表示一个询问元素。

    输出格式

    共q行,每行包含两个整数,表示所求元素的起始位置和终止位置。
    如果数组中不存在该元素,则返回“-1 -1”。

    数据范围

    1≤n≤100000
    1≤q≤10000
    1≤k≤10000

    思路:

    1.找到左端点:
    - 区间[0 , n-1]
    - 判断条件:num[mid] >= tar
    2.找到右端点:
    - 区间[左端点, n-1]
    - 判断条件:num[mid] <= tar

    代码:

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int N = 100010;
    int n,q;
    int num[N];
    int main(void)
    {
        scanf("%d %d",&n,&q);
        for(int i = 0;i < n;i++)
            scanf("%d",&num[i]);
        while(q--)
        {
            int tar;
            scanf("%d",&tar);
            
            //找左端点
            int l = 0,r = n-1;
            while(l < r)
            {
                int mid = l + r >> 1;
                if(num[mid] >= tar) 
                    r = mid;
                else
                    l = mid + 1;
            }
            if(num[l] != tar)
                printf("-1 -1
    ");
            else
            {
                printf("%d ",l);
                //找右端点
                r = n - 1;
                while(l < r)
                {
                    int mid = l + r + 1 >> 1;
                    if(num[mid] <= tar)
                        l = mid;
                    else
                        r = mid - 1;
                }
                printf("%d
    ",l);
            }
        }
    	return 0;
    
    }
    

    机器人跳跃问题

    机器人正在玩一个古老的基于DOS的游戏。
    游戏中有N+1座建筑——从0到N编号,从左到右排列。
    编号为0的建筑高度为0个单位,编号为 i 的建筑高度为H(i)个单位。
    起初,机器人在编号为0的建筑处。
    每一步,它跳到下一个(右边)建筑。
    假设机器人在第k个建筑,且它现在的能量值是E,下一步它将跳到第k+1个建筑。
    如果H(k+1)>E,那么机器人就失去H(k+1)-E的能量值,否则它将得到E-H(k+1)的能量值。
    游戏目标是到达第N个建筑,在这个过程中能量值不能为负数个单位。
    现在的问题是机器人以多少能量值开始游戏,才可以保证成功完成游戏?

    输入格式

    第一行输入整数N。
    第二行是N个空格分隔的整数,H(1),H(2),…,H(N)代表建筑物的高度。

    输出格式

    输出一个整数,表示所需的最少单位的初始能量值。

    数据范围

    (1≤N,H(i)≤10^5)
    (1≤N,H(i)≤10^5)

    思路:

    1.分析题目

    • 递推关系:
      • (H(k+1)>E,E-(H(k+1)-E)=2E-H(k+1))
      • (H(k+1)<=E,E+(E-H(k+1))=2E-H(k+1))
      • 每跳跃一次,E有2倍的关系,需要注意会不会产生值溢出的问题
    • 如果机器人具有的能量数是所有建筑高度的MAX,那么机器人一定可以通过所有建筑,因为对于建筑高度小于能量数的,能量数会增加,对于建筑高度等于能量数,能量数会增加0。
    • 且机器人具有的最小能量数是应该是1,因为建筑最低高度是1

    2.二分法

    • 区间[1 , max]上,具有二段性和单调性(假设能量E0刚好可以通过建筑,那么Ek>E0,使用能量Ek一定可以通过,反之一定不能通过)
    • 判断条件 cal(mid) >= 0,即对于一个能量值mid,定义一个函数cal(int x)进行判断,如果在通过所有建筑后还剩能量大于0,说明初始能量值可能还可以继续缩小。

    代码

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    const int N = 100010;
    int n;
    int num[N];
    int cal(int x,int ma)             
    {
        for(int i = 1; i <= n;i++)
        {
            if(x >= num[i])
                x += (x - num[i]);
            else
                x -= (num[i] - x);
            if(x < 0 || x >= ma)     //如果超过max,则一定可以通过;如果小于0,则一定不行
                break;
        }
        return x;
    }
    
    int main(void)
    {
        scanf("%d",&n);
        int ma = 0;
        for(int i = 1 ;i <= n; i++ )
        {
            scanf("%d", &num[i]);
            ma = max(ma,num[i]);
        }
        //二分法    
        int l  = 1,r = ma;
        while(l < r)
        {
            int mid = (l + r) >> 1;
            if(cal(mid,ma) >= 0) 
                r = mid;
            else
                l = mid + 1;
        }
        printf("%d",r);
        return 0;
        
    }
    

    我遇到的坑

    判断函数cal()中一开始是没有

    if(x < 0 || x >= ma)     
         break;
    

    数据会导致int x溢出,x累加后的范围确实不好想;我使用long long x结果也溢出了。最后才想到加上上面的判断条件就好了,既快也不会溢出。

    分巧克力

    儿童节那天有K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。小明一共有N块巧克力,其中第i块是Hi x Wi的方格组成的长方形。

    为了公平起见,小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们。切出的巧克力需要满足:

    1. 形状是正方形,边长是整数
    2. 大小相同

    例如一块6x5的巧克力可以切出6块2x2的巧克力或者2块3x3的巧克力。

    当然小朋友们都希望得到的巧克力尽可能大,你能帮小Hi计算出最大的边长是多少么?

    输入

    第一行包含两个整数N和K。(1 <= N, K <= 100000)
    以下N行每行包含两个整数Hi和Wi。(1 <= Hi, Wi <= 100000)
    输入保证每位小朋友至少能获得一块1x1的巧克力。

    输出

    输出切出的正方形巧克力最大可能的边长。

    思路

    和前面那道题很类似了,巧克力大小的区间范围是[1,max],对区间内巧克力的边长进行二分,如果
    判断条件cal(mid) >= k,即可以分出边长为mid的巧克力大于k个小朋友,那么就可能可以把巧克力在分的更大一些。

    代码

    #include <cstring>
    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #include <climits>
    using namespace std;
    typedef long long LL;
    const int N = 100010;
    int num[N][2];
    int n,k;
    LL cal(int dat)
    {
        LL sum = 0;
        for(int i = 0; i < n;i++)
        {
            int x = num[i][0]/dat;
            int y = num[i][1]/dat;
            sum +=  x * y;
            if(sum >= k)
                break;
        }
        return sum;
    }
    int main(void)
    {
        scanf("%d %d", &n,&k);
        int ma = 0;               
        for(int i = 0; i < n;i++)
        {
            scanf("%d %d",&num[i][0],&num[i][1]);
            ma = max(ma,min(num[i][0], num[i][1]));//取所有巧克力中宽的最大值
        }
        int l = 1,r = ma;            
        while(l < r)
        {
            int mid = (l + r + 1) >> 1;
            if(cal(mid) >= k)
                l = mid;
            else
                r = mid - 1;
        }
        printf("%d",l);
        return 0;
    }
    

    遇到的坑

    一开始边界区间没有找准,我认为是右边应该是所有巧克力边长中的最小值,因为如果要使用每一块巧克力,那么肯定要找所有边长的最小值,但是事实上,一些某一边长小的巧克力完全可以不要嘛。这个是提交后发现的,还是考虑不周全。

    四平方和

    四平方和定理,又称为拉格朗日定理:
    每个正整数都可以表示为至多4个正整数的平方和。
    如果把0包括进去,就正好可以表示为4个数的平方和。

    比如:
    5 = 0^2 + 0^2 + 1^2 + 2^2
    7 = 1^2 + 1^2 + 1^2 + 2^2
    (^符号表示乘方的意思)

    对于一个给定的正整数,可能存在多种平方和的表示法。
    要求你对4个数排序:
    0 <= a <= b <= c <= d
    并对所有的可能表示法按 a,b,c,d 为联合主键升序排列,最后输出第一个表示法

    程序输入为一个正整数N (N<5000000)
    要求输出4个非负整数,按从小到大排序,中间用空格分开

    思路:

    我一开始就往二分去想,没有想出来,稍微想了一下枚举,但是感觉会超时,就看了题解,如下:
    1.先计算枚举的时间复杂度:(sqrt{5*10^6} = 2240)
    估算三重for循环的时间复杂度是O((10^9)),显然超时了(实际上,因为大部分的N的a,b都很小,大部分数据是不会超时的);

    2.所以要改进枚举,一般的方法是 使用空间换取时间。在本题中:

    • 可以先枚举c和d,将 (c^2+d^2) 的值c,d保存下来(顺序存储、哈希)
    • 在枚举a,b,计算 (t = n - a^2-b^2) ,使用二分法或者哈希来判断t是否已经存储。
    • 如果t存在,那么直接输出。

    方法1-哈希

    使用(c^2+d^2)的值直接作为哈希函数,需要注意的是可能有多种方法可以构成某个值,但是只保留第一个,因为第一个字典序最小。

    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    const int N = 5000010;
    int num[N][2];
    int n;
    int main(void)
    {
        scanf("%d",&n);
        int m = 0;
        for(int c = 0; c * c <= n;c++ )
            for(int d = c;c * c + d * d <= n;d++)
            {
                int tmp = c*c + d*d;
                if(num[tmp][0] == 0 && num[tmp][1] == 0) //在哈希数组还未赋值的情况下,才允许赋值
                {
                    num[tmp][0] = c;
                    num[tmp][1] = d;
                }
            }
        for(int a = 0;a * a <= n;a++)
            for(int b = a; a * a + b * b <= n;b++)
            {
                int t = n - a * a - b * b;
                if(t == 0 || (num[t][0] != 0 || num[t][1] != 0))   //判断t是否存在,存在则直接输出a b c d
                {
                    printf("%d %d %d %d",a,b,num[t][0],num[t][1]);
                    return 0;
                }
            }
        return 0;
    }
    

    方法2-二分
    二分的话,在于第一步保存(c^2+d^2)的值时,如果有重复的,如何做到只保留字典序最小的,这个我能想到的只有遍历,看大佬的程序使用了我不知道的C++语法,贴出来,以供学习。

    //作者:yxc
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #include <cmath>
    
    using namespace std;
    
    const int N = 2500010;
    
    struct Sum
    {
        int s, c, d;
        bool operator< (const Sum &t)const
        {
            if (s != t.s) return s < t.s;
            if (c != t.c) return c < t.c;
            return d < t.d;
        }
    }sum[N];
    
    int n, m;
    
    int main()
    {
        cin >> n;
    
        for (int c = 0; c * c <= n; c ++ )
            for (int d = c; c * c + d * d <= n; d ++ )
                sum[m ++ ] = {c * c + d * d, c, d};   //不清楚这里是怎么处理有多种方法可以构成一个值的
    
        sort(sum, sum + m);
    
        for (int a = 0; a * a <= n; a ++ )
            for (int b = 0; a * a + b * b <= n; b ++ )
            {
                int t = n - a * a - b * b;
                int l = 0, r = m - 1;
    			//二分
                while (l < r)
                {
                    int mid = l + r >> 1;
                    if (sum[mid].s >= t) r = mid;
                    else l = mid + 1;
                }
                if (sum[l].s == t)
                {
                    printf("%d %d %d %d
    ", a, b, sum[l].c, sum[l].d);
                    return 0;
                }
            }
    
        return 0;
    }
    
    
  • 相关阅读:
    装饰者模式
    代理模式
    享元模式
    模板模式
    命令模式
    建造者模式
    单例模式
    观察者模式
    迭代器模式
    访问者模式
  • 原文地址:https://www.cnblogs.com/zy200128/p/12594311.html
Copyright © 2011-2022 走看看