7.24 二分搜索

二分搜索作用:降低时间复杂度到log(n);求满足条件的最大的最小值,或是最小最大值;

设计一个bool  judge 函数,判断该点是否合法(满足条件)

A题:

还记得我们新生赛上的这题Averyboy的筷子这题吗?众所周知,Averyboy是一个非常的男孩,既然是一个非常的男孩,那么他就会有许多奇葩的爱好。比如收藏筷子。现在美美旸有n双筷子,编号为1-n,同一双筷子编号相同,美美旸把这些筷子放成一排。现在美美旸对av-boy说,如果你能解决我给你出的一个问题,这些筷子都归你。美美旸的问题是:把这些筷子通过一些操作,使得最后这些筷子是同一双的一定相连。每次的操作是,你可以从这2n支筷子中任意取出一支,剩下的筷子自动合并在一起,然后把这支筷子插入任意一个位置,包括两端。(操作次数无限制。)为了***难av-boy,美美旸故意定了一个条件,每次只能取出编号大于等于x的筷子。现在你的问题是找出最大的x(1 <= x <= n),使得av-boy一定能顺利拿到筷子。

 

 这道题的意思就是说选择一个编号为x的筷子,编号大于等于x的筷子都可以随意移动,那就相当于把编号大于等于x的筷子都删掉,看剩下的筷子编号相同的是不是都连在一起就行,因为剩下的筷子是移动不了的,只能自动合并。由此我们设计出bool judge函数
接下来二分搜索最大的x即可,注意筷子是2*n个
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=1e5+10;
 4 int a[maxn];
 5 int b[maxn];
 6 int k,n;
 7 bool judge(int k)
 8 {
 9     int i;
10     int cnt=1;
11     for(i=1;i<=2*n;i++)      
12     {
13         if(a[i]<k)
14         {
15             b[cnt++]=a[i];
16         }
17     }
18     cnt--;
19     for(i=1;i<=cnt-1;i++)
20     {
21         if(i&1)
22         {
23             if(b[i+1]!=b[i])return false;
24         }
25     }
26     return true;
27 }
28 int bisearch()
29 {
30     int l=1,r=n;
31     int ans=0;
32     while(l<=r)
33     {
34         int mid=(l+r)>>1;
35         if(judge(mid))
36         {
37             ans=mid;
38             l=mid+1;
39         }
40         else r=mid-1;
41     }
42     return ans;
43 }
44 int main()
45 {
46    int t;
47    cin>>t;
48    while(t--)
49    {
50        scanf("%d",&n);
51        for(int i=1;i<=2*n;i++)scanf("%d",&a[i]);
52        int ans=bisearch();
53        printf("%d\n",ans);
54    }
55     return 0;
56 }
View Code

 

B题:

现在有N种物品,每一种物品有一个基础价值a[i],当买k件物品时,第i件物品的最终价值为a[i] + k * i,现在给你S元,你最多能买多少件物品.

 最原始的问题,确定一个k,则每件物品的最终价值都被确定;
设计bool  judge 函数,对物品的最终价值进行排序,选择最小的k件求和,若和>s,则不合法;
二分查找最大的k;注意如果i*j的乘积很大,则i,j都要开long long ,不然会爆
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
long long a[maxn];
long long b[maxn];
long long n;
long long s;
bool judge(long long k)
{
    int i;
    for(i=1;i<=n;i++)
    {
        b[i]=a[i]+i*k;
    }
    sort(b+1,b+n+1);
    long long sum=0;
    for(i=1;i<=k;i++)
    {
        sum+=b[i];
    }
    if(sum<=s)return true;
    else return false;
}
long long bisearch()
{
    int l=1,r=n;
    long long ans=0;
    while(l<=r)
    {
        long long mid=(l+r)>>1;
        if(judge(mid))
        {
            ans=mid;
            l=mid+1;
        }
        else r=mid-1;
    }
    return ans;
}
int main()
{

    int t;
    cin>>t;
    while(t--)
    {
        scanf("%d%lld",&n,&s);
        int i;
        for(i=1;i<=n;i++)scanf("%d",&a[i]);
        long long ans=bisearch();
        printf("%d\n",ans);
    }
    return 0;
}
View Code

 

C题:

众所周知,averyboy是一个非常男孩。天外天给你他一个问题。问题如下:给你一个长度为N的序列a[1]~a[N]和一个整数sum,我们称一个区间为averyboynb区间,当且仅当这个区间的所有数的和不超过sum。现在你需要找出一个区间和最大的averyboynb区间。

 

枚举左端点,二分查找右端点;

对于每一个左端点,二分求解满足条件的最大的右端点

 

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 const int maxn=1e5+10;
 5 int n;
 6 ll sum;
 7 int a[maxn];
 8 ll pre[maxn];
 9 bool judge(int n,int left)
10 {
11     if(pre[n]-pre[left-1]<=sum)return true;
12     else return false;
13 }
14 int main()
15 {
16     int t;
17     cin>>t;
18     while(t--)
19     {
20         scanf("%d%lld",&n,&sum);
21         int i;
22         pre[0]=0;
23         for(i=1;i<=n;i++)
24         {
25             scanf("%d",&a[i]);
26             pre[i]=pre[i-1]+a[i]; //前缀和数组 很大程度上减小时间复杂度
27         }
28         ll res=0;
29         for(i=1;i<=n;i++)  //枚举左端点
30         {
31             int l=i;
32             int r=n;
33             int ans=0;
34             while(l<=r)     //二分查找右端点
35             {
36                 int mid=(l+r)>>1;
37                 if(judge(mid,i))
38                 {
39                     ans=mid;
40                     l=mid+1;      //找满足条件的最大的右端点
41                 }
42                 else r=mid-1;
43             }
44             if(ans>0)res=max(res,pre[ans]-pre[i-1]);
45         }
46         if(res>0)printf("%lld\n",res);
47         else printf("averyboynb\n");
48     }
49     return 0;
50 }
View Code

D题:

美旸旸现在在腾讯实习。他每工作一天,公司就会给他发一天的工资,但是,当天的工资不一定要当天领,可以等到下次领的时候一起领,但是每次领工资时,之前所有没有领的工资必须这次都领完。现在给你美旸旸工作N天的工资a[1]~a[N],然后给你一个整数M,代表美旸旸一共要领M次工资(不能多也不能少)并且最后一定要把N天的工资领完,毕竟都是美旸旸赚的血汗钱。也就是第N天他是一定要领工资的。(不然就领不完工资了。。。)。现在问题是让你帮美旸旸选择一种领工资的方案,使得这M次领的工资的最大值最小,毕竟美旸旸是一个低调的人,他不想一次领太多钱。你能帮助他吗?

Sample Input

1
7 5
100
400
300
100
500
101
400

Sample Output

500

此题为典型的二分搜索题,即找最小最大值

设计bool judge函数,判断当最大值为k时,需要领工资的次数是否<=M,是则合法,否则非法。

尽可能最多地领工资,一直领到存储的值会大于最大值k

二分查找最小的最大值k

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 const int maxn=1e5+10;
 5 int n,tmax;
 6 int a[maxn];
 7 bool judge(ll x)
 8 {
 9     int i;
10     ll sum=0;
11     int t=0;
12     for(i=1;i<=n;i++)
13     {
14         if(sum+a[i]>x)
15         {
16             t++;        //在领到a[i]这个工资之前,必须要领一次工资了
17             sum=a[i];   //领完一次工资之后现在账户存储的值  等价于sum=0;sum+=a[i];
18         }
19         else sum+=a[i];  //尽可能的存储最大的工资
20     }
21     if(sum)t++;
22     if(t>tmax)return false;
23     else return true;
24 }
25 int main()
26 {
27     int t;
28     cin>>t;
29     while(t--)
30     {
31         int i;
32         scanf("%d%d",&n,&tmax);
33         for(i=1;i<=n;i++)scanf("%d",&a[i]);
34         int r=0,l=-1;
35     ll ans=0;
36     for(i=1;i<=n;i++)
37     {
38         r+=a[i];  // 上界是所有的工资之和
39         l=max(l,a[i]); //下界是单日工资中最大的那一个
40     }
41     while(l<=r)
42     {
43         int mid=(l+r)>>1;    //算出来的mid不一定是某些工资的和,但是最后一定会减小到那些存在的和。
44         if(judge(mid))
45         {
46             ans=mid;
47             r=mid-1;       //二分查找满足条件的最小的最大值
48         }
49         else l=mid+1;
50     }
51     printf("%lld\n",ans);
52 
53     }
54     return 0;
55 }
View Code

 

posted @ 2018-07-25 19:38  raincle  阅读(139)  评论(0编辑  收藏  举报