蓝桥杯零基础冲国赛-第16天

基础准备:

C++中的sort办法进行,要使用这个sort办法要引入< algorithm > 头文件

官方原型:

template <class RandomAccessIterator>
  void sort (RandomAccessIterator first, RandomAccessIterator last);//fist为起始位置,last为末尾位置加一,也可以理解为要排序数组的长度	
template <class RandomAccessIterator, class Compare>
  void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);//comp为自定义排序规则的函数

简单用法:

sort默认是从小到大

int num[105] = {99, -2, 7, 3, 5};
sort(num + 0, num + 5);

从大到小,在sort加入第三个参数

bool cmp(const int &a, const int &b) {//在排好序以后,a表示排序在前的元素,b表示排序在后的元素,加const是官方建议的,不加编译也能通过
    return a > b;//这里注意一个问题就是只能写大于号或小于号,如果写等于号可能出现死排序
}
int num[105] = {99, -2, 7, 3, 5};
sort(num, num + 5, cmp);

自定义结构用法:

结构体/类

第一种用法///
struct node {
    int x, y;
};
bool cmp_node(const node &a, const node &b) {
    if (a.x == b.x) {return a.y > b.y;} //这行代码就是为了一个满足如果x的值相等按y的值排序的需求
    return a.x > b.x;
}
node t[10];
t[0].x = 5, t[0].y = 1;
t[1].x = 7, t[1].y = 0;
t[2].x = 6, t[2].y = 9;
sort(t, t + 3, cmp_node);

第二种用法///
struct node {
    int x, y;
    bool operator< (const node &b) const {
        return this->x > b.x;
    }//重载小于号,这涉及C++语法,可选
};
node t[10];
t[0].x = 5, t[0].y = 1;
t[1].x = 7, t[1].y = 0;
t[2].x = 6, t[2].y = 9;
sort(t, t + 3);

sort办法训练题目:海贼oj网站需要借会员的私聊

375 奖学金

代码演示:

#include<iostream>
#include<algorithm>
using namespace std;

struct student {
    int num, c, m, e, sum;
};
bool cmp(const student &a, const student &b) {
    if(a.sum == b.sum) {
        if(a.c == b.c) {
            return b.num < a.num;
        }
        return a.c > b.c;
    }
    return a.sum > b.sum;
}
student stu[305];
int n;
int main() {
    cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> stu[i].c >> stu[i].m >> stu[i].e;
        stu[i].sum = stu[i].c + stu[i].m + stu[i].e;
        stu[i].num = i;
    }
    sort(stu + 1, stu + n + 1, cmp);
    for (int i = 1; i <= 5; i++) {
        cout << stu[i].num << " " << stu[i].sum << "\n";
    }
    return 0;
}

381谁拿了最多奖学金

代码演示:

#include<iostream>
#include<algorithm>
#include<string>
using namespace std;

struct student {
    string name;
    int avg, cla, paper, num, sum;
    char west, off;
};
student stu[105];
int n;
int func(int i) {
    int t = 0;
    if (stu[i].avg > 80 && stu[i].paper > 0) t += 8000;
    if (stu[i].avg > 85 && stu[i].cla > 80) t += 4000;
    if (stu[i].avg > 90) t += 2000;
    if (stu[i].avg > 85 && stu[i].west == 'Y') t += 1000;
    if (stu[i].cla > 80 && stu[i].off == 'Y') t += 850;
    return t;
}
bool cmp (const student &a, const student &b) {
    if (a.sum == b.sum) {
        return a.num < b.num;
    }
    return a.sum > b.sum;
}
int main () {
    int ans = 0;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> stu[i].name >> stu[i].avg >> stu[i].cla >> stu[i].off >> stu[i].west >> stu[i].paper;
        stu[i].num = i;
        stu[i].sum = func(i);
        ans += stu[i].sum;
    }
    sort(stu + 1, stu + n + 1, cmp);
    cout << stu[1].name << "\n" << stu[1].sum << "\n" << ans; 
    return 0;
}

380大统领投票

代码演示:

#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
struct node{
    int cnt;
    string num;
};
int n;
node x[105];
bool cmp (const node &a, const node &b) {
    if (a.num.size() == b.num.size()) {
        return a.num > b.num;
    }
    return a.num.size() > b.num.size();
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> x[i].num;
        x[i].cnt = i;
    }
    sort(x + 1, x + n + 1, cmp);
    cout << x[1].cnt << "\n" <<x[1].num;
    return 0;
}
二分折半查找

本质:删掉不存在的区间

作用:解决连续问题,如单调函数

核心思想:从小到大排序,以中间值arr[mid]和查找目标x比较,不断地缩小一半查找范围

操作思路:

排序(c++用sort函数)

设定一个头指针min和一个尾指针max

取中间值位置 mid = (min + max) / 2

如果中间值不是查找目标x,此时头尾指针做调整,缩小一半范围

调整:如果arr[mid] < x ,min = mid +1

如果arr[mid] > x, max = mid -1

如果arr[mid] == x,找到结果

终止条件:min > max

意义:二分折半查找实现单调函数(平方根)问题

核心代码:

while(l <= r) {
    //int mid = (l + r) / 2//因为l + r可能会溢出
    int mid = (r - l) / 2 + l;
    if (num[mid] > x) r = mid - 1;
    if (num[mid] < x) l = mid + 1;
    if (num[mid] == x) return num[mid];
}

二分折半查找训练题目:海贼oj网站

388奇怪的刮刮乐

代码演示:

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int n, m, numm[100005], numn[100005], f;
long long ans;

int main() {
    scanf("%d%d", &m, &n);
    for (int i = 0; i < m; i++) {
        scanf("%d", &numm[i]);
    }
    for (int i = 0; i < n; i++) {
        scanf("%d", &numn[i]);
    }
    sort(numn, numn + n);
    for (int i = 0; i < m; i++) {
        int l = 0, r = n - 1;
        while (l <= r) {
            int mid = (r - l) / 2 + l;
            if (numm[i] == numn[mid]) {
                if (f == 1) {
                    printf(" ");
                }
                f = 1;
                printf("%d", numm[i]);
                ans += numm[i];
                break;
            }
            if (numm[i] < numn[mid]) {
                r = mid - 1;
            } else {
                l = mid + 1;
            }
        }
    }
    printf("\n%lld\n", ans);
    return 0;
}

386吃瓜群众

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
struct node {
    int num, cnt;
};
int n, m;
node wm[100005];

bool cmp (const node &a, const node &b) {
    return a.num < b.num;
}

int main () {
    scanf("%d%d", &m, &n);
    for (int i = 1; i <= m; i++) {
        scanf("%d", &wm[i].num);
        wm[i].cnt = i;
    }
    sort(wm + 1, wm + m + 1, cmp);
    for (int i = 1; i <= n; i++) {
        int t, l = 1, r = m, f = 0;
        scanf("%d", &t);
        while (l <= r) {
            int mid = (r - l) / 2 + l;
            if(wm[mid].num == t) {
                printf("%d\n", wm[mid].cnt);
                f = 1;
                break;
            }
            if (wm[mid].num < t) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        if(f == 0) {
            printf("0\n");
         }
    }
    return 0;
}

泛型二分查找

出现题型:二分答案——函数数组

理解泛型二分前提是理解二分查找本质:删掉不存在的区间

①、在排序数组里面查找第一个大于等于给定值的元素的位置(00000000111111)

判定:寻找的目标答案的右边都满足目标条件

l = 0, r = nums.size() - 1;
while (l <= r) {
    int mid = (r + l) / 2;//r + l可能会溢出,可以用l + (l + r) / 2来优化,一般数组没有2的32次方那么长,所以这样的优化一般适合非数组,比如函数
    if (nums[mid] >= target) {//为什么有等号呢,因为可能在nums[mid]前还有多个nums[mid]
        r = mid - 1;//为什么不是r = mid?因为如果r = mid可能导致死循环(因为循环条件用的是l <= r,在l == r时就可能出现死循环,如果想用r = mid可以用循环条件 l != r或 l < r)。那问题又来了,mid - 1最终不久会错过第一个1了吗,其实不是,如果r指向了0那么最终l(L)就会和r错开,最终l指向第一个1。所以r指向的位置永远不可能是target
    } else {
        l = mid + 1;//如果找不到,l指向的值就是数组里面第一个大于等于target的值
    }
    return l;
}

②、在排序数组里面查找最后一个小于等于给定值的元素的位置 在例如 111111111000000 中找最后一个1

判定:寻找的目标答案的左边都满足目标条件

while (l < r) {
    mid = (l + r + 1) / 2;//因为除以奇数结果偏小原因,防止死循环(比如数组就只有 1 0,这时mid为0,num[mid]为1,所以l就为mid为0,导致mid一直是0)
    if (num[mid] <= target) {
        l = mid;
    }
    if (num[mid] > target) {
        r = mid - 1;
    }
}

泛型二分折半查找训练题目:海贼oj网站

387吃瓜群众升级版

代码演示:

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
struct node {
    int num, cnt;
};
int m, n;
node wm[100005];
bool cmp (const node &a, const node &b) {
    return a.num < b.num;
}
int main () {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++ ) {
        scanf("%d", &wm[i].num);
        wm[i].cnt = i;
    }
    wm[0].cnt = 0;
    wm[0].num = 2000000000;
    sort (wm, wm + n + 1, cmp);
    for (int i = 1; i <= m; i++) {
        int t, l = 0, r = n;
        scanf("%d", &t);
        while (l != r) {
            int mid = (l + r) / 2;
            if (wm[mid].num >= t) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        printf("%d\n", wm[l].cnt);
    }
    return 0;
}
二分答案

390原木切割

题目描述:

某林业局现在 N根原木,长度分别为 Xi,为了便于运输,需要将他们切割成长度相等的 M根小段原木(只能切割成整数长度,可以有剩余),小段原木的长度越大越好,现求小段原木的最大长度。例如,有 3根原木长度分别为 6,15,22,现在需要切成 8 段,那么最大长度为 5。

首先来枚举一下所有长度可能出现的情况:

长度l和段数的关系式
f ( x ) = ∑ i = 0 N ( X [ i ] / l ) f(x) = {\sum_{i=0}^{N}(X[i] / l)} f(x)=i=0N(X[i]/l)

长度 1  2  3  4 5 6 7 8 9 ... 22
段数 43 21 14 9 8 6 5 3 3 ... 1

可以发现,每个长度都对应一个段数,也就是说按切割长度从1到最长的原木长度去枚举一定可以找到对应的段数,反过来对应的切割长度就是答案。相同理解,既然是在一堆数中找目的数,那么就可以用到二分查找。回归到二分的本质:删掉不存在的区间。那么这道题怎么去删除呢?然后就是判断是不是一种泛型二分,重要是怎么去区分是(0000011111)还是(11111100000)或者不是。所以可以借助这样一个小技巧:在例子的的答案左右取一个浮点数来分辨

长度 1  2  3  4 4.9 5 5.1 6 7 8 9 ... 22
段数 43 21 14 9  8  8  7  6 5 3 3 ... 1

所以可以判定为1111110000(如果思维能力强的人会想到这么一个想法,答案一定是和某个原木是刚好整除,那么在边角料的可控范围内小的切割长度都是可以和答案一致的,而大的切割长度就会使这个原木的切割段数变小)

代码演示:

#include<iostream>
#include<cstdio>
using namespace std;
int n, m, l = 1, r = 0;
int num[100005];
//获取各个棍子截段后的总段数
int func (int x) {
    int t = 0;
    for (int i = 0; i < n; i++) {
        t += num[i] / x;
    }
    return t;
}
//根据f(x)函数数组原理,通过长度x找到对应的总段数f(x)的值,实质为线性方程,一个递减函数
int bs() {
    while (l != r) {
        int mid = (r + l + 1) / 2;
        int t = func(mid);
        if (t >= m) {
            l = mid;
        } else {
            r = mid - 1;
        }
    }
    return r;
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++) {
        scanf("%d", &num[i]);
        r = max(num[i], r);
    }
    printf("%d", bs());
    return 0;
}

二分答案总结:二分答案就是一个函数数组问题,也就是说找到一个关系式(所以一个重点就是找到关系式),在这个关系中根据自变量x(1,2,3,4…)来寻找答案(第二个重点就是找到自变量区间),在最终答案之前的答案都是临时答案,这个临时答案只是一个移动左右指针的作用,最后都需要考虑一下是不是泛型二分。其实这也是二分查找的来源,也就是我们在高一数学中的二分解方程。只是我们习惯把之前的线性问题转换成了离散数学问题。

题目训练:

389暴躁的程序猿

393切绳子

82伐木

389数列分段

394跳石头

预告:二分专题练习、KMP、Sunday、Shift-[And/Or]算法

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

扑天鹰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值