zoukankan      html  css  js  c++  java
  • 基础算法 02_二分法、三分法

    更多二分题目戳我

    二分是一种思想,而非模板

    在什么情况下使用二分?需要同时满足两个条件:上下界[L,R]确定,函数在[L,R]内是单调的。

    二分法模板:

    int left = ?, right = ?;//给L和R确定边界
      while (left < right)
      {
        int ans;//ans记录答案
        int mid = (left + right) >> 1;
        if (check(mid)) {
          ans = mid;//记录答案
          ...//移动left(或者right)
        }
        else ...//移动right(或者left)
      }

    所以,二分的难点就在于如何建立模型(寻找单调性作为mid)和check()条件(如何缩小边界),在写二分的时候,可能会用上其他的算法或者数据结构,比如在DP中,或者图论中。

    二分法的典型应用有:最大值最小化最小值最大化

     

    最大值最小化(让最大值尽可能的小)

    https://www.luogu.com.cn/problem/P1462

    题目:

    题目背景
    在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量
    
    有一天他醒来后发现自己居然到了联盟的主城暴风城
    
    在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛
    
    题目描述
    在艾泽拉斯,有n个城市。编号为1,2,3,...,n。
    
    城市之间有m条双向的公路,连接着两个城市,从某个城市到另一个城市,会遭到联盟的攻击,进而损失一定的血量。
    
    每次经过一个城市,都会被收取一定的过路费(包括起点和终点)。路上并没有收费站。
    
    假设1为暴风城,n为奥格瑞玛,而他的血量最多为b,出发时他的血量是满的。
    
    歪嘴哦不希望花很多钱,他想知道,在可以到达奥格瑞玛的情况下,他所经过的所有城市中最多的一次收取的费用的最小值是多少。
    
    输入格式
    第一行3个正整数,n,m,b。分别表示有n个城市,m条公路,歪嘴哦的血量为b。
    
    接下来有n行,每行1个正整数,fi。表示经过城市i,需要交费fi元。
    
    再接下来有m行,每行3个正整数,ai,bi,ci(1<=ai,bi<=n)。表示城市ai和城市bi之间有一条公路,如果从城市ai到城市bi,
    或者从城市bi到城市ai,会损失ci的血量。 输出格式 仅一个整数,表示歪嘴哦交费最多的一次的最小值。 如果他无法到达奥格瑞玛,输出AFK。 输入输出样例 输入 #1复制 4 4 8 8 5 6 10 2 1 2 2 4 1 1 3 4 3 4 3 输出 #1复制 10 说明/提示 对于60%的数据,满足n≤200,m≤10000,b≤200 对于100%的数据,满足n≤10000,m≤50000,b≤1000000000 对于100%的数据,满足ci≤1000000000,fi≤1000000000,可能有两条边连接着相同的城市。

      

    分析:

    题目说让求交费最多一次的最小值,是最大值最小化问题,我们二分这个费用。

    check函数怎么写呢?
    如果有比他大的,就换一条路走,走不通返回false
    如果血量消耗完,返回false
    但是这样显然是不对的,血量问题没有得到解决,血量够用不够用,
    和路费的二分没有与之相匹配的单调性。
    ----------------------------------------------
    我们换种思路来求,还是以路费作为二分的依据。
    在所有的路径中,总有一个点的点权最大为x[]
    我们要找到x[]中最小的那个x
    我们将点权按照从小到大的顺序排个序,然后二分点权
    check函数怎么写呢。
    我们在二分到某个点权为x的点v的时候,将点权大于x的点删掉
    在剩下的点中求一边最短路,如果不能从1走到n,返回false
    如果求得的最短路的总边权大于等于我们的血量,返回false
    然后都满足,返回true   

    小计:

    这个题,我做了大概有三个小时,分析这个题的时候,并没有多花时间(当然,也是因为看了其他人的博客,找了思路)。思路是对的,但是自己的编码能力确实不行,中间一直在错。
    一开始,我想的是将每个点的点权从小到大排个序,然后枚举下标,l=0,r=n+1 。然后如果r在1到n之间的话,就是有解,否则就是没解。但是我忽略了在dijkstra算法的时候筛去哪些大于mid的值的时候,cost的下标早就已经变化了。
    还有就是dijkstra用的居然如此的生疏,最开始明明不需要将st标记为true的,我给标记为true了,这里的st数组的作用是如果这个点已经扩展过了其他所有点,那就给他标记上。并不是spfa算法中的标记是否在队列中。
    还有一点就是,记录图的时候,用strust还是不熟悉啊,用错了都。

     代码:

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    #include <vector>
    
    using namespace std;
    
    typedef pair<int, int> PII;
    
    const int N = 100010, inf = 0x3f3f3f3f;
    
    int n, m, blood;
    int val[N];
    int h[N], e[N], ne[N], w[N], tot;
    
    void add(int a, int b, int c)
    {
      e[++ tot] = b, w[tot] = c, ne[tot] = h[a], h[a] = tot;
    }
    
    int dist[N];
    bool st[N];
    
    bool check(int mid)
    {
      // printf("%d
    ", mid);
      memset(st, 0, sizeof st);
      memset(dist, 0x3f, sizeof dist);
      dist[1] = 0;
      priority_queue<PII, vector<PII>, greater<PII>> q;
      q.push({0, 1});
    
      while (q.size())
      {
        PII t = q.top();
        q.pop();
    
        int x = t.second;
        if (st[x]) continue;
        st[x] = true;
    
        if (val[x] > mid) continue;
        // printf("%d && %d && %d
    ", mid, x, val[x]);
    
        for (int i = h[x]; ~i; i = ne[i])
        {
          int y = e[i], z = w[i];
          if (val[y] > mid) continue;
          if (dist[y] > dist[x] + z)
          {
            dist[y] = dist[x] + z;
            q.push({dist[y], y});
          }
        }
      }
      return dist[n] < blood;
    }
    int main()
    {
      memset(h, -1, sizeof h);
      scanf("%d%d%d", &n, &m, &blood);
      for (int i = 1; i <= n; i ++ )
      {
        int x; scanf("%d", &x);
        val[i] = x;
      }
    
      for (int i = 1; i <= m; i ++ )
      {
        int x, y, z; scanf("%d%d%d", &x, &y, &z);
        add(x, y, z), add(y, x, z);
      }
    
      int l = 0, r = inf;
      if (!check(r))
      {
        puts("AFK");
        return 0;
      }
      while (l < r)
      {
        int mid = l + r >> 1;
        // puts("--------------------------");
        if (check(mid)) r = mid;
        else l = mid + 1;
      }
    
      printf("%d
    ", r);
      return 0;
    }
    

      

    最小值最大化(让最小值尽量大)

    题目:

    题目描述
    Farmer John建造了一个有N(2<=N<=100,000)个隔间的牛棚,这些隔间分布在一条直线上,坐标是x1,...,xN (0<=xi<=1,000,000,000)。
    
    他的C(2<=C<=N)头牛不满于隔间的位置分布,它们为牛棚里其他的牛的存在而愤怒。为了防止牛之间的互相打斗,Farmer John想把这些牛安置在指定的
    隔间,所有牛中相邻两头的最近距离越大越好。那么,这个最大的最近距离是多少呢? 输入格式 第1行:两个用空格隔开的数字N和C。 第2~N+1行:每行一个整数,表示每个隔间的坐标。 输出格式 输出只有一行,即相邻两头牛最大的最近距离。 输入输出样例 输入 #1复制 5 3 1 2 8 4 9 输出 #1复制 3

      

    分析:

    要让最小值最大化,我们二分的就是这个最小值,如果所有牛的间距都大于等于mid,就是合法的(那mid就是最小值)

    我们的目标就是让这个mid更大,二分就好

    代码:

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 
     5 using namespace std;
     6 
     7 const int N = 100010, inf = 0x3f3f3f3f;
     8 
     9 int n, c;
    10 int a[N], b[N];
    11 
    12 bool check(int mid)
    13 {
    14   int cnt = 1, sum = 0;
    15   for (int i = 1; i <= n - 1; i ++ )
    16   {
    17     sum += b[i];
    18     if (sum >= mid)
    19     {
    20       cnt ++;
    21       sum = 0;
    22     }
    23   }
    24 
    25   return cnt >= c;
    26 }
    27 
    28 int main()
    29 {
    30   scanf("%d%d", &n, &c);
    31   for (int i = 1; i <= n; i ++ )
    32   {
    33     scanf("%d", &a[i]);
    34   }
    35 
    36   sort(a + 1, a + 1 + n);
    37   for (int i = 1; i < n; i ++ )
    38   {
    39     b[i] = a[i + 1] - a[i];
    40   }
    41 
    42   int l = 0, r = inf;
    43   while (l < r)
    44   {
    45     int mid = l + r + 1>> 1;
    46     // printf("%d %d %d
    ", mid, l, r);
    47     if (check(mid)) l = mid;
    48     else r = mid - 1;
    49   }
    50 
    51   printf("%d
    ", r);
    52 
    53   return 0;
    54 }
    View Code

    三分法:

    三分法常用来求解单峰函数或单谷函数的极值问题。

    三分法通过将区间三等分(r - l) / 3,然后让mid1=l + (r - l) / 3, 让mid2 = r - (r - l) / 3;

    如果这个函数是先增后减的,那么存在单峰。

    如果f(mid1)<f(mid2)说明极值点v一定在mid1的右侧,如果f(mid1)>f(mid2)说明极值点一定在mid2的左侧。

    三分法的时间复杂度是O(log(3))的。

    https://www.luogu.com.cn/problem/P3382

    题目:

    题目描述
    如题,给出一个 N 次函数,保证在范围 [l,r] 内存在一点 x,使得 [l,x] 上单调增,[x,r] 上单调减。试求出 x 的值。
    
    输入格式
    第一行一次包含一个正整数 N 和两个实数 l,r,含义如题目描述所示。
    
    第二行包含 N + 1 个实数,从高到低依次表示该 N 次函数各项的系数。
    
    输出格式
    输出为一行,包含一个实数,即为 x 的值。若你的答案与标准答案的相对或绝对误差不超过 10^{-5} 则算正确。
    
    输入输出样例
    输入 #1复制
    3 -0.9981 0.5
    1 -3 -3 1
    输出 #1复制
    -0.41421
    说明/提示
    对于 100% 的数据,6<= N <=13,函数系数均在 [−100,100] 内且至多 15 位小数,∣l∣,∣r∣≤10 且至多 15 位小数.l <= r
    
    【样例解释】
    
    
    
    如图所示,红色段即为该函数 f(x) = x^3 - 3 x^2 - 3x + 1在区间 [-0.9981, 0.5]上的图像。
    
    当 x = -0.41421 时图像位于最高点,故此时函数在 [l,x] 上单调增,[x,r] 上单调减,故 x=−0.41421,输出 −0.41421。
    

    分析: 

    实数三分模板题。

    代码:

    #include <cstdio>
    
    using namespace std;
    
    int n;
    double l, r;
    double a[55];
    
    double f(double mid)
    {
      double ans = 0;
      for (int i = n; i >= 0; i -- )
      {
        ans = ans * mid + a[i];
      }
      return ans;
    }
    int main()
    {
      scanf("%d%lf%lf", &n, &l, &r);
      for (int i = n; i >= 0; i -- )
        scanf("%lf", &a[i]);
    
      while (r - l > 1e-6)
      {
        double mid1 = l + (r - l) / 3, mid2 = r - (r - l) / 3;
        if (f(mid1) > f(mid2)) r = mid2;
        else l = mid1;
      }
    
      printf("%.5lf
    ", r);
    
      return 0;
    }

    三分:

    [曲线](https://ac.nowcoder.com/acm/contest/951/C)

    题目:

    链接:https://ac.nowcoder.com/acm/contest/951/C
    来源:牛客网

    题目描述

    明明做作业的时候遇到了n个二次函数Si(x)=ax2+bx+c,他突发奇想设计了一个新的函数F(x)=max⁡{Si(x)},i=1…n
    明明现在想求这个函数在[0,1000]的最小值,要求精确到小数点后四位,四舍五入。

    输入描述:

    输入包含T组数据,每组第一行一个整数n;

    接下来n行,每行3个整数a,b,c,用来表示每个二次函数的3个系数。注意:二次函数有可能退化成一次。

    输出描述:

    每组数据输出一行,表示新函数F(x)的在区间[0,1000]上的最小值。精确到小数点后四位,四舍五入。
    示例1

    输入

    2
    1
    2 0 0
    2
    2 0 0
    2 -4 2

    输出

    0.0000
    0.5000

    备注:

    对于50%的数据,1≤n≤100;对于100%的数据,1≤T≤10,1n100000,0≤a≤100,0≤∣b∣≤5000,0≤∣c∣≤5000

    分析:

    最大值最小问题

    唯一明白的就是F函数就是一个单谷函数。。。明白了这一点,就OK了

    代码:

    #include <cstdio>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 100010, inf = 1 << 30;
    const double eps = 1e-6;
    
    int n;
    double a[N], b[N], c[N];
    
    double f(double x)
    {
      double ans = -inf;
      for (int i = 1; i <= n; i ++ )
        ans = max(ans, a[i] * x * x + b[i] * x + c[i]);
      return ans;
    }
    void work()
    {
      scanf("%d", &n);
      for (int i = 1; i <= n; i ++ )
      {
        scanf("%lf%lf%lf", &a[i], &b[i], &c[i]);
      }
    
      double l = 0, r = 1000;
      while (r - l > eps)
      {
        double d = (r - l) / 3;
        double mid1 = l + d, mid2 = r - d;
        if (f(mid1) > f(mid2)) l = mid1;
        else r = mid2;
      }
    
      printf("%.4lf
    ", f(r));
    }
    
    int main()
    {
      int T; scanf("%d", &T);
      while (T -- )
      {
        work();
      }
      return 0;
    }
    

      三分套三分,我觉得你要偶尔性的回顾一下了

  • 相关阅读:
    小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_5-9.使用JWT生成用户Token回写客户端
    小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_5-8.用户模块开发之保存微信用户信息
    小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_5-7.授权登录获取微信用户个人信息实战
    小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_5-6.微信扫码登录回调本地域名映射工具Ngrock
    Vmware Briged方式使虚拟机上网
    设置VIM的配色方案
    SecureCRT辅助解决方案
    ARM标准汇编与GNU汇编
    arm:c语言和汇编混合编程
    assert()用法
  • 原文地址:https://www.cnblogs.com/Iamcookieandyou/p/14719934.html
Copyright © 2011-2022 走看看