《算法笔记上机实验指南》第4章 入门篇(2)---算法初步 4.5二分

pat A1085

生词:

sequence 一连串
parameter 范围

题意:

在这里插入图片描述

输入:

1.输入n个数字和p

输出:

满足最大值<=最小值*P的序列中个数最多的个数

解题思路1:

在这里插入图片描述
1.设置n的上界maxn=100010,定义n,p,a[maxn]为全局变量
2.i,j分别表示前后遍历数组a的下标,根据题意可知a[j]<=a[i]*p,并且要求j-i最大
3.利用二分的思想:找出从i+1~n-1这个范围内找出第一个比a[i]*p大的数字的下标j(其实真正满足a[j]<=a[i]*p的数字的下标为j-1,但是最后的结果是要找到最大的长度,即为此时的j-i)
4.设置ans为1,然后每次与二分函数binarySearch(int i, long long x)返回的值进行比较,得出最大的,最后输出

参考代码1:

#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100010;
int n,p,a[maxn];
/*
    解题思路:


    题意:
    1.找出满足最大值<=最小值*P的序列中个数最多的个数

*/
//binarySearch函数在[i+1,n-1]范围内查找第一个大于x的数的位置
int binarySearch(int i, long long x)
{
    if(a[n-1]<=x)      //如果所有数都不大于x,返回n
        return n;
    int l=i+1,r=n-1,mid;               //在[i+1,n-1]内查找
    while(l<r)
    {
        mid=(l+r)/2;
        if(a[mid]<=x)   //如果a[mid]<=x,说明第一个大于x的数只可能在mid后面
        {
            l=mid+1;   //左端点即为mid+1
        }
        else
        {
            r=mid;          //右端点即为mid
        }
    }
    return l;     //由于while结束时l==r,因此返回l或者r皆可
}
int main()
{
    cin >> n >> p;
    for(int i=0; i<n; i++)
    {
        cin >> a[i];
    }
    sort(a,a+n);       //递增排序
    int ans=1;        //最大长度,初值为1(表示至少有1个数字)
    for(int i=0; i<n; i++)
    {
        //在a[i+1]-a[n-1]中查找第一个超过a[i]*p的数,返回其位置给j
        int j=binarySearch(i,(long long)a[i]*p);
        ans=max(ans,j-i);     //更新最大长度,最后找的是满足条件的序列的最大长度而非最大数字
    }
    cout << ans << endl;
    return 0;
}

注意事项:

a[maxn]不用设置为long long类型,因为调用函数时,用long long强制转化
在这里插入图片描述

解题思路2:

在这里插入图片描述

参考代码2:

#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100010;
int n,p,a[maxn]; //
int main()
{
    cin >> n >> p;
    for(int i=0; i<n; i++)
        cin >> a[i];
    sort(a,a+n);
    int ans=1;
    for(int i=0; i<n; i++)
    {
        int j=upper_bound(a+i+1,a+n,(long long)a[i]*p)-a; //使用upper_bound(a+i,a+j,x)-a返回的是第一个大于x的数的坐标
        ans=max(ans,j-i);
    }
    cout << ans;
    return 0;
}

知识总结:

upper_bound(a+i,a+j,x)-a返回的是第一个大于x的数的坐标

#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100010;
int n,p,a[maxn]; //
int main()
{
    int x,i;
    cin >> n >> x;
    for(i=0; i<n; i++)
        cin >> a[i];
    cout << upper_bound(a,a+n,x)-a; //使用

    return 0;
}

在这里插入图片描述


pat A1010

单词:

1.decimal 十进位的
2. radix 基数

题意:

在这里插入图片描述

输入:

1.输入4个值 N1 N2 tag radix

输出:

1.一个数如果能通过进制的转化成为另一个数,则输出该进制;否则输出impossible

解题思路:

本题的二分思想体现在:现有两个数字,一个是目标数字,一个是变化数字,想办法找出变化数字所能有的进制数的上界和下界,通过二分的方法与目标数字进行比较,如果小表示进制小,则left=mid+1;如果大表示进制大,则right=mid-1
对于同一个字符长度的字符数组,如果进制数越大,则其转化为10进制也越大
在这里插入图片描述
解题思路:
1.题目中说N1,N2不超过10位数字,因为后面涉及进制的转化,所以设置为long long类型
2.设置inf表示long long类型取值的上界:是64位的整型,取值范围为-2^63 ~ (2^63 - 1)。
3.6个函数:
1.void init() 将09,az与0~35的对应
2.LL convertNum10(char a[],LL radix, LL t) 将数字字符串a转化为进制为radix的数
3.int cmp(char N2[],LL radix, LL t) 将N2通过convertNum10转化为radix进制放到num中,注意num大,返回1;num小,返回-1;相同,返回0
4.LL binarySearch(char N2[], LL left, LL right, LL t) 利用二分结合函数cmp的返回值确定最终的进制数ans
5.int findLargestDigit(char N2[]) 求出N2中最大数的位数,最终确定进制的下界
4.主函数:
1.先进行数字的映射
2.输入N1,N2,tag,radix,如果tag为2,则交换N1和N2
3.t为N1的radix进制数
4.low为进制的下界
5.high为low,t中较大值+1表示进制的上界
6.调用二分进制数得到ans,根据ans进行输出

参考代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
LL Map[256];
LL Inf=(1LL<<63)-1;
int tag;
LL radix;
char N1[11],N2[11],temp[11];    //注意:N1,N2是字符数组不是LL类型

void init()  //将0--9和a--z映射到0--35
{
    for(char c='0'; c<='9'; c++)
    {
        Map[c]=c-'0';       //将'0'~'9'映射到0--9
    }
    for(char c='a'; c<='z'; c++)
    {
        Map[c]=c-'a'+10;      //将a~z映射到10~35
    }
}

LL convertNum10(char a[],LL radix, LL t)
{
    //将a转换为10进制,t为上界
    LL ans=0;
    int len=strlen(a);
    for(int i=0; i<len; i++)
    {
        ans=ans*radix+Map[a[i]];   //进制转化
        if(ans<0 || ans>t)
            return -1;    //溢出或超出N1的10进制
    }
    return ans;
}

int cmp(char N2[],LL radix, LL t) //N2的10进制与t比较
{
    LL num=convertNum10(N2,radix,t);    //将N2转化为10进制
    if(num<0)
        return 1;        //溢出,肯定是N2>t,表示进制大了
    if(t>num)
        return -1;          //t较大,返回-1,表示进制小了
    else if(t==num)
        return 0;    //相等,返回0
    else    //num较大的话返回1
        return 1;       //num较大,返回1,表示进制大了
}

LL binarySearch(char N2[], LL left, LL right, LL t)    //t表示将N1转化为进制为radix的十进制数
{
    //二分求解N2的进制
    LL mid;
    while(left<=right)
    {
        mid=(left+right)/2;
        int flag=cmp(N2,mid,t);  //判断N2转化为10进制后与t比较
        if(flag==0)    //说明t==m
            return mid;       //找到解,返回mid
        else if(flag==-1)   //说明t>num
            left=mid+1;    //往右字区间继续查找
        else   //说明num较大
            right=mid-1;     //往左字区间继续查找
    }
    return -1;   //解不存在
}

int findLargestDigit(char N2[])
{
    //求最大位数
    int ans=-1,len=strlen(N2);
    for(int i=0; i<len; i++)
    {
        if(Map[N2[i]]>ans)
        {
            ans=Map[N2[i]];
        }
    }
    return ans+1;    //最大的位数为ans,说明进制数的底线是ans+1
}
int main()
{
    init();
    cin >> N1 >> N2 >> tag >> radix;
    if(tag==2)
    {
        strcmp(N1,temp);
        strcmp(N2,N1);
        strcmp(N2,temp);
    }
    LL t=convertNum10(N1,radix,Inf);
    LL low=findLargestDigit(N2);
    LL high=max(low,t)+1;
    int ans=binarySearch(N2,low,high,t);
    if(ans==-1)
        cout << "Impossible" << endl;
    else
        cout << ans << endl;
    return 0;
}



知识总结:

1.strcmp和strcpy的用法:

//strcpy的用法:
#include<iostream>
#include<cstring>    //必须加入刺头文件
using namespace std;
int main()
{
    char s[1000],s1[1000]="ipad pro";
    strcpy(s,s1);  //将s1拷贝给s,只能用于数字字符串
    cout << s;
    return 0;
}

//strcmp的用法:
#include<iostream>
#include<cstring>    //必须加入刺头文件
using namespace std;
int main()
{
    //两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇'\0'为止
    //如果s<s1,则返回-1;如果s=s1,则返回0; 如果s>s1,则返回1
    char s[100],s1[100];
    cin >> s >> s1;
    cout << strcmp(s,s1);
    return 0;
}

2.将0-9和a-z分别映射为0–35

void init()  //将0--9和a--z映射到0--35
{
    for(char c='0'; c<='9'; c++)
    {
        Map[c]=c-'0';       //将'0'~'9'映射到0--9
    }
    for(char c='a'; c<='z'; c++)
    {
        Map[c]=c-'a'+10;      //将a~z映射到10~35
    }
}

3.将一个字符串数组a,按照进制radix,上界为t转化为long long类型的整数

LL convertNum10(char a[],LL radix, LL t)
{
    //将a转换为10进制,t为上界
    LL ans=0;
    int len=strlen(a);
    for(int i=0; i<len; i++)
    {
        ans=ans*radix+Map[a[i]];   //进制转化
        if(ans<0 || ans>t)
            return -1;    //溢出或超出N1的10进制
    }
    return ans;
}

4.给出一个字符数组,能求出它的进制的最小数
比如111011010,他的最小进制为2 ; 1223,它的最小进制为3,当然也可以为10

#include<iostream>
#include<cstring>
using namespace std;
int main()
{
    char N2[100]="1101";
    int ans=-1;
    int len=strlen(N2);
    for(int i=0; i<len; i++)
    {
        if((N2[i]-'0')>ans)
            ans=(N2[i]-'0');
    }
    cout << ans+1;
    return 0;
}

5.计算2^63-1和typedef

typedef long long LL;
LL inf=(1LL << 63)-1;   //long long的最大值2^63-1

6.数字,大小写字母的ascii表
在这里插入图片描述


pat A1044

题意:

在这里插入图片描述

输入:

1.输入n,然后下面输入n个值
2.输入m,表示所要构成的数字之和

输出:

1.输出连续的数字能使其和为m的所有下标,如果没有为m的,就直接输出大于m的最小值的所有下标

解题思路:

在这里插入图片描述
解题思路:
1,设置upper_bound函数
1.作用:利用二分找出第一个比x大的数的位置下标
2.初始化数组sum[0]=0
3.从下标为1开始输入数字
4.设置后面的for:
1.先找出nearS:
1.用upper_bound从i~n+1的位置,找出比sum[i]+S大的第一个数的下标
2.如果sum[j-1]-sum[i-1]的值为S,则令nears=s,并退出循环
3.如果值不相同,则就找出比S大的最小值,然后也令nearS为此值即sum[j]-sum[i-1]
5.后面利用upper_bound函数,找出比nearS大的第一个数,但此时计算的时候还是用sum[j-1]-sum[i-1]看是否为nearS,如果等于就
输出

参考代码:

#include<cstdio>
#include<iostream>
using namespace std;
const int N=100010;
int sum[N];
int n,S,nearS=100000010;
//upper_bound函数返回(L,R)内第一个大于x的位置
/*
    解题思路:
    1,设置upper_bound函数
        1.作用:利用二分找出第一个比x大的数的位置下标
    2.初始化数组sum[0]=0
    3.从下标为1开始输入数字
    4.设置后面的for:
        1.先找出nearS:
            1.用upper_bound从i~n+1的位置,找出比sum[i]+S大的第一个数的下标
            2.如果sum[j-1]-sum[i-1]的值为S,则令nears=s,并退出循环
            3.如果值不相同,则就找出比S大的最小值,然后也令nearS为此值即sum[j]-sum[i-1]
    5.后面利用upper_bound函数,找出比nearS大的第一个数,但此时计算的时候还是用sum[j-1]-sum[i-1]看是否为nearS,如果等于就
    输出

*/
int upper_bound(int L, int R, int x)
{
    int left=L,right=R,mid;
    while(left<right)
    {
        mid=(left+right)/2;
        if(sum[mid]>x)
        {
            right=mid;
        }
        else
        {
            left=mid+1;
        }
    }
    return left;
}

int main()
{
    cin >> n >> S;    //元素个数,和值S
    sum[0]=0;      //初始化sum[0]=0
    for(int i=1; i<=n; i++)
    {
        cin >> sum[i];
        sum[i]+=sum[i-1];   //求sum[i]
    }
    for(int i=1; i<=n; i++)
        cout << sum[i] << " ";
    cout << endl;
    //sum[j-1]-sum[i-1],这里所求和为S,则i-1~j-1就为和S
    //sum[j]-sum[i-1],表示从i到j的数字之和
    //下面第一个for语句的作用是如果能找出和为S的数组,就让nears和S,否则就找出比S大的第一个数
    for(int i=1; i<=n; i++)  //枚举左端点,先找出nearS,如果是满足的nearS则直接让nearS=S,否则就是找出比S大的最小nears
    {
        int j=upper_bound(i,n+1,sum[i-1]+S);     //找出第一个比参考数大的第一个数的位置
        if(sum[j-1]-sum[i-1]==S)   //查找成功(注意是j-1而不是j)
        {
            nearS=S;      //最接近s的值就是S
            break;
        }   
        else if(j<=n && sum[j]-sum[i-1]<nearS)  //统计出比S大的最小数值
        {
            //存在大于s的解并小于nearS
            nearS=sum[j]-sum[i-1];   //更新当前nearS
        }
    //直接找值满足nearS数组的下标
    for(int i=1; i<=n; i++)
    {
        int j=upper_bound(i,n+1,sum[i-1]+nearS);     //求右端点
        if(sum[j-1]-sum[i-1]==nearS)  //查找成功
            cout << i << "-" << j-1 << endl;       //输出左端点和右端点(注意是j-1而不是j)
    }
    return 0;
    }
}

注意事项:

1.注意n的取值范围,所以才设置数组长度时要大于100000
在这里插入图片描述

2.注意在写二分的函数时,每次要改变mid的值,所以要在循环内设置mid=(left+right)/2

在这里插入图片描述


pat A1048

题意:

输入数字n,m;后面连续输出n个数字
在n个数字中找出v1+v2==m,并且v1<=v2的数,如果没有则直接输出no solution

输入:

1.输入n,然后下面输入n个值
2.输入m,表示所要构成的数字之和

输出:

如果v1+v2==n,并且v1<=v2的

解题思路:

在这里插入图片描述
解题思路:
0.这道题用常规的二分思想,就是while(i<=j) 然后 a[mid]>x,则right=left-1,如果a[mid]<x,则left=mid+1,最后,如果值相等,则返回他的下标
1.这道题的二分思想是,先输入数组,然后将数字进行排序,然后遍历每一个数字,用二分的思想从这个数后面的
一个位置一直到结束,开始查找它的差(m-v[i])。如果找到则放回下标,否则返回-1,一旦找到,就是最小的,直接输出然后就退出循环
2.最后根据i的位置是否为n,来是否输出no solution

参考代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[100010];
/*

*/
int Bin(int left, int right, int key)
{
    int mid;
    while(left<=right)
    {
        mid=(left+right)/2;
        if(a[mid]==key)
            return mid;
        else if(a[mid]>key)
            right=mid-1;
        else
            left=mid+1;
    }
    return -1; //表示咩有找到
}
int main()
{
    int i,n,m;
    cin >> n >> m;
    for(int i=0; i<n; i++)
        cin >> a[i];
    sort(a,a+n);
    for(i=0; i<n; i++)
    {
        int pos=Bin(i+1,n-1,m-a[i]);
        if(pos!=-1)  //如果找到合适的并且i!=pos,其中i!=pos表示2*v1==m,然后题目要求是v1<=v2
        {
            cout << a[i] << " " << a[pos] << endl;
            break;
        }
    }
    if(i==n)
        cout << "No Solution";
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值