二分基础模板+深化+一些题型汇总

看了小白书二分,之后被那些神奇的判断条件看的有点蒙,然后在很长一段时间对二分有点恐惧,上次总结赛,碰到基础二分也没敢开。趁寒假末尾将二分这个基础算法总结一下,将一些题型套路,陈列下,可能有点长。。。

本文重点对各题型的判断条件的讲解

第0部分:

题型陈列,和一些例题:

1.假定一个解并判断是否可行 :POJ 1064  POJ 3122 Pie  (这两个均为实数二分)

2.最大化最小值 POJ2456 POJ3258 POJ3273 POJ 3104 POJ3045

3.最大化平均值 POJ2976 POJ3111.

4.查找第K大值POJ3579 POJ3685

5.最小化第K大值 POJ 2010 POJ3662

6.略带小技巧的简单数组查找: HDU - 2141 UVA - 1152 

7.枚举方程解 HDU - 2199  SDNU1416

8.三分+其他 POJ 1759 POJ3484

第一部分

模板简介:

我经常使用的模板:

1.整数定义域:

int l = 1, r = n, ans;
while(l <= r)
    {

        int mid = (l + r) / 2;
        if(check(mid) >= k)  ans = mid, r = mid - 1;
        else l = mid + 1;
    }

2.实数域二分

double binary(double l, double r) 
{
	while(r - l <= dlt)
	{
		double mid = (l + r) / 2;
		if(calc(mid)) r = mid;
		else l = mid;
	}
	return l;
}
for(int i=0;i<100;i++)
{

    double mid=(l+r)/2;
    if(calc(mid)) r=mid;else l=mid;
}

在实数域上的二分简单,重要的是确定好所需的精度dlt,每次根据在mid 上的判定选择r=mid  或 l=mid分支之一即可,一般需要保留k位小数是,则取dlt=10^-(k+2)

有时精度不容易确定或者表示,就干脆采用循环固定次数的二分方法,也是一种相当不错的策略。这种方法得到的结果的精度通常比设置dlt更高。

2.拆抄的另一种实数域上的模板

整数域上的二分,分三步 (其中mid最好是>>1 而不是/2, 因为>>1 是向下取整,而/2是向0取整,在负数时很有用)

      (1)通过分析具体问题,确定左右半段哪一个是可行区间,以及mid归属那一半段。

      (2)根据分析结果,选择“r=mid, l=mid+1, mid=(l+r)>>1” 和 “l=mid,  r=mid-1, mid=(l+r+1)>>1”两个配套形式之一。

      (3)二分终止条件是l==r, 该值就是答案所在位置。

    (一定要注意区间的选择,区间[0,n] 和 [1,n+1] 这两个不能够乱用 ,0和n+1 都是越界下标,只有在特定情境下若没有找到才会等于越界下标)

例子:

1.在单调递增序列a中查找>=x的数中的最小的一个:
 

while(l<r)
{
   int mid=(l+r)>>1;
   if(a[mid]>=x) r=mid; else l=mid+1;  
}
return a[l];

2. 在单调递增序列a中查找<=x的数中的最大的一个:

while(l<r)
{
    int mid=(r+l+1)>>1;
    if(a[mid]<=x) l=mid; else r=mid-1;
}
return a[l];

第二部分:

分题型解析:

1.假定一个解并判断是否可行:

题型讲解:最基础的二分搜索, 在一定的区间内利用二分思想来判断,找到的值,是否符合题意,并不断进行优化,缩小。

可作为二分的入门,理解。

(1)POJ 1064

题意切割出K条长度相等的绳子,这K条绳子最长有多长

唯一需要注意的是精度。

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

int n, k;
double a[10010];
bool check(double mid)
{
    int sum = 0;
    for(int i = 0; i < n; i++)
    {
        sum = sum + (int)(a[i] / mid);
    }

    return sum < k;
}
int main()
{
    while(scanf("%d%d", &n, &k) != EOF)
    {
        double r = 0, l = 0;
        for(int i = 0; i < n; i++)
        {
            scanf("%lf", &a[i]);
            r = max(r, a[i]);
        }
        for(int i = 0; i < 100; i++)
        {
            double mid = (r + l) / 2;
            if(check(mid)) r = mid;
            else l = mid;
        }
        printf("%.2lf\n", floor(l * 100) / 100);
    }
    return 0;
}

(2)POJ 3122 Pie

这题和上题一样的思路和题型

#include<bits/stdc++.h>
using namespace std;
const int maxn = 10010;
const double PI = acos(-1.0); // 派的值

int t, n, f, r;
double a[maxn], sum;
bool check(double mid)
{
    int ans = 0;
    for(int i = 0; i < n; i++)
    {
        ans += (int)(a[i] / mid); // 这里需要注意下,虽然我也没搞太懂懂为啥不加强制转化不行但                                                            
                                 //确实挺恶心的这错误
    }
    return ans >= (f + 1);
}
int main()
{
    scanf("%d", &t);
    while(t--)
    {
        sum = 0.0;
        scanf("%d%d", &n, &f);
        for(int i = 0; i < n; i++)
        {
            scanf("%d", &r);
            a[i] = PI * double(r * r);
            sum += a[i];
        }
        double l = 0.0, r = sum;
        while(r - l >= 1e-7)
        {
            double mid = (l + r) / 2;
            if(check(mid)) l = mid;
            else r = mid;
        }
        printf("%.4f\n", r);
    }
    return 0;
}

2.最大化最小值

思想:贪心法确定位置,使需要确定的值达到最优化。

在这里讲两个题,基础的POJ2456 牛舍安放题,和进阶版POJ3104 烘干衣服。

(1)POJ2456

题意:有N间牛舍,M条牛。给定牛舍的位置,安放牛使得牛相隔的尽可能的远。

题目分析:将牛舍的位置进行排序,然后在间隔下初始算一间,那就是在选M-1间牛舍。

判断条件讲解:因为要牛距离最大那初始处有一条牛,才能尽可能做到牛之间相隔的距离最大,所以我们只需要在选M - 1个牛舍,我们可以枚举牛舍间的距离,来判定能不能在这么样的距离下,安放下牛。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 100100;
const int INF = 0x3f3f3f3f;

int N, C, a[maxn];
bool check(int mid)
{
    int sum = 0, last = 0, cnt;
    for(int i = 1; i < C; i++)
    {
        cnt = last + 1;
        while(cnt < N && a[cnt] - a[last] < mid)
        {
            cnt++;
        }
        if(cnt == N)
            return false;
        last = cnt;
    }
    return true;
}
int main()
{
    while(scanf("%d%d", &N, &C) != EOF)
    {
        for(int i = 0; i < N; i++)
        {
            scanf("%d", &a[i]);
        }
        sort(a, a + N);
        int l = 0, r = INF, ans;
        while(l <= r)
        {
            int mid = (l + r) / 2;
            if(check(mid)) ans = mid, l = mid  + 1;
            else  r = mid - 1;
        }
        printf("%d\n", ans);
    }
    return 0;
}

(2)POJ3104

题意:冬天烘干衣服,在炉子里每分水分减k,在外面自然风干每分减1;

题意分析:

1.我们可以枚举会花费多少分钟m来烘干所有衣服。

2.然后减去在m分钟内会自然风干的水分,看其还需要多少次加热器风干。

3.需要注意的是,在加热器干时就不会再自然风干所以需要判定的是(k - 1)。

4.还需注意的是当k == 1时需特判。

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 100010;

long long n, a[maxn], k;
bool check(long long mid)
{
    long long sum = 0;
    for(int i = 0; i < n; i++)
    {
//        a[i] -= mid;
        if(a[i] > mid)
        {
            sum += (a[i] - mid) / (k - 1);
            if((a[i] - mid) % (k - 1) != 0)
                sum += 1;
        }
    }
    return sum <= mid;
}
int main()
{
    while(scanf("%lld", &n) != EOF)
    {
        long long l = 0, r = 0, ans;
        for(int i = 0; i < n; i++)
        {
            scanf("%lld", &a[i]);
            r = max(r, a[i]);
        }
        scanf("%d", &k);
        if(k==1)//k=1对下面的情况不易判断,所以在此特判
        {
            printf("%lld\n",r);
            continue;
        }
        while(l <= r)
        {
            long long mid = (l + r) >> 1;
            if(check(mid)) ans = mid, r = mid - 1;
            else l = mid + 1;
        }
        printf("%lld\n", ans);
    }
    return 0;
}

3.最大化平均值

思想:

1.单位质量的价值:

2.我们需要二分查找的就是筛选后达到的单位质量的价值x

所以判断条件:

变形后

所以我们就可以将的值排序后贪心选取前k个不为0的数。

这种题型都类似模板题,这里就简单讲解一个:

POJ3111

题意:有n个珠宝,求出k个必须留下的珠宝。

题意分析见上方思想。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 100010;

int n, k;
double v[maxn], w[maxn];
bool used[maxn];
struct nod
{
    double num;
    int it;
};
bool cmp(nod a, nod b)
{
    return a.num > b.num;
}
bool check(double x)
{
    nod y[maxn];
    memset(used, false, sizeof(used));
    for(int i = 1; i <= n; i++)
    {
        y[i].num = v[i] - x * w[i];
        y[i].it = i;
    }

    sort(y + 1, y + n + 1, cmp);
    double sum = 0;
    for(int i = 1; i <= k; i++)
    {
        sum += y[i].num;
        used[y[i].it] = true;
    }
    return sum >= 0;

}

int main()
{
    while(scanf("%d%d", &n, &k) != EOF)
    {
        for(int i = 1; i <= n; i++)
        {
            scanf("%lf%lf", &v[i], &w[i]);
        }
        double l = 0, r = 1e8, ans;
        while(r - l >= 1e-7)
        {
            double mid = (l + r) / 2;
            if(check(mid)) ans = mid, l = mid;
            else r = mid;
        }
        int flag = 0;
        for(int i = 1; i <= n; i++)
        {
            if(used[i])
            {
                if(flag)
                    printf(" ");
                flag = 1;
                printf("%d", i);
            }
        }
        printf("\n");
//        printf("%lf",ans);
    }
    return 0;
}

4.查找第K大值

思想:一般为2次二分,第一次查找符合题意的值,第二次来二分查找符合题意的第K大的值。

例题 POJ - 3579 

题目解析链接见我另一篇博客

5.最小化第K大的值

例题链接

未完待续。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值