算法训练一(贪心、二分)(含解题思路)(上)

目录

7-1最少失约(贪心)

AC代码:

7-2删数问题(贪心)

7-3区间覆盖(贪心)

AC代码:

7-7加油站之最小加油次数(贪心+优先队列)

AC代码:

7-8求解删数问题(贪心)

AC代码:

7-9跳一跳(模拟)

AC代码:

7-10装箱问题(简单贪心)

AC代码:

7-11Keven的援助(模拟)

AC代码:

7-12Swan学院社团招新(贪心)

AC代码:

7-13区间选点(贪心)

AC代码:

7-14喷水装置(贪心)

 AC代码:


因题集题目较多,下半部分题集请移步这里:

算法训练一(贪心、二分)(含解题思路)(下)_清晨喝碗粥的博客-CSDN博客

7-1最少失约(贪心)

某天,诺诺有许多活动需要参加。但由于活动太多,诺诺无法参加全部活动。请帮诺诺安排,以便尽可能多地参加活动,减少失约的次数。假设:在某一活动结束的瞬间就可以立即参加另一个活动。

输入格式:

首先输入一个整数T,表示测试数据的组数,然后是T组测试数据。每组测试数据首先输入一个正整数n,代表当天需要参加的活动总数,接着输入n行,每行包含两个整数i和j(0≤i<j<24),分别代表一个活动的起止时间。

输出格式:

对于每组测试,在一行上输出最少的失约总数。

输入样例:

1
5
1 4
3 5
3 8
5 9
12 14

输出样例:

2

思路:将开始时间从小到大排序,如果开始时间相同的话按终止时间从小到大排,如果后一个活动的起始时间刚好大于上一个参加的活动的终止时间(如果想要参加更可能多的活动的话)那么参加这个活动是最优的,因为已经做过排序处理,后面的活动的终止时间都>=此次判断的活动,最后用count记录最多能参加多少活动的个数,用活动数减去count即最少失约总数。

AC代码:

#include<bits/stdc++.h>
using namespace std;
int solve(int n) {
    int i, j, k, count = 1;
    vector<vector<int>>nums(n, vector<int>(2, 0));
    for (i = 0; i < n; i++) {
        for (j = 0; j < 2; j++) {
            cin >> nums[i][j];
        }
    }
    sort(nums.begin(), nums.end(), [](const vector<int> &a, const vector<int> &b) {
        return a[1] < b[1];
    });
    int temp = nums[0][1];
    for (i = 1; i < n; i++) {
        if (nums[i][0] >= temp) {
            temp = nums[i][1];
            count++;
        }
    }
    return n - count;
}
int main()
{
    int i, j, k, n, t;
    cin >> t;
    vector<int>res;
    while (t--) {
        cin >> n;
        res.push_back(solve(n));
    }
    for (i = 0; i < res.size(); i++) {
        cout << res[i] << endl;
    }

    system("pause");
    return 0;
}

7-2删数问题(贪心)

有一个长度为n(n <= 240)的正整数,从中取出k(k < n)个数,使剩余的数保持原来的次序不变,求这个正整数经过删数之后最小是多少。

输入格式:

n和k

输出格式:

一个数字,表示这个正整数经过删数之后的最小值。

输入样例:

178543 4

输出样例:

13

思路:简化版的7-8,题解及代码详见7-8

7-3区间覆盖(贪心)

设 x1​,x2​,...,xn​ 是实直线上的n个点。用固定长度的闭区间覆盖这n个点,至少需要多少个这样的固定长度闭区间?

输入格式:

第1行有2个正整数n(n<50)和k,表示有n个点,且固定长度闭区间的长度为k。

接下来的1行中有n个整数 ai​(−2000<ai​<2000) ,表示n个点在实直线上的坐标(可能相同)。

输出格式:

最少区间数。

输入样例:

7 3
1 2 3 4 5 -2 6

输出样例:

3

思路:对排序后的这n个点进行操作,用s记录这n个点中的每个任意区间,如果判断nums[i]时发现在此区间i++,最后用count记录固定区间个数即可。

AC代码:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int i, x, k, n, s, count = 0;
    cin >> n >> k;
    vector<int>nums;
    for (i = 0; i < n; i++) {
        cin >> x;
        if (!nums.empty() && x == nums[nums.size() - 1])
            continue;
        else
            nums.push_back(x);
    }
    n = nums.size();
    sort(nums.begin(), nums.end());
    for (i = 0, s = nums[0]; i < n; s = nums[i]) {
        s += k;
        count++;
        while (i < n && s >= nums[i]) {
            i++;
        }
    }
    cout << count << endl;


    system("pause");
    return 0;
}

7-7加油站之最小加油次数(贪心+优先队列)

一辆汽车要行驶L单位距离。最开始时,汽车上有P单位汽油,每向前行驶1单位距离消耗1单位汽油。如果在途中车上的汽油耗尽,汽车就无法继续前行,即无法到达终点。途中共有N个加油站,加油站提供的油量有限,汽车的油箱无限大,无论加多少油都没问题。给出每个加油站距离终点的距离L和能够提供的油量P,问卡车从起点到终点至少要加几次油?如果不能到达终点,输出-1。

输入格式:

第一行输入N;
接下来N行分别输入两个整数L和P。
最后一行表示汽车的起点到终点的位置L和油量P。

输出格式:

输出到达城镇所需的最少站点数,如果车无法到达城镇,则输出-1。

输入样例1:

4
4 4
5 2
11 5
15 10
25 10

输出样例1:

2

输入样例2:

4
4 4
5 2
11 5
15 10
25 1

输出样例2:

-1

思路:这道题是一道经典贪心题,题目要求我们计算最低的加油次数,因此我们需要每次加油都尽可能加最多的油才能保证单次加油走的更远并且加油次数最少。

因此我们维护一个优先队列(最大堆),每次路过加油站先不加油,将加油站中的油量放入最大堆中,当油量不足以支持汽车走到下一个加油站时,我们选择加堆中最多的油(也就是堆顶元素)直到汽车能走到下一个加油站或者目的地,如果当堆为空是仍无法到达,输出-1,否则输出加油次数。

AC代码:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int i, n, L, P, count = 0;
    cin >> n;
    vector<vector<int>>nums(n, vector<int>(2, 0));
    for (i = 0; i < n; i++) {
        cin >> nums[i][0] >> nums[i][1];
    }
    cin >> L >> P;
    sort(nums.begin(), nums.end(), greater<vector<int>>());
    priority_queue<int, vector<int>, less<int>>Q;
    int start = L;
    for (i = 0; i <= nums.size(); i++) {
        int item = i < nums.size() ? nums[i][0] : 0;
        P -= start - item;
        while (P < 0 && !Q.empty()) {
            P += Q.top();
            Q.pop();
            count++;
        }
        if (P < 0) {
            cout << -1 << endl;
            return 0;
        }
        if (i < n) {
            Q.push(nums[i][1]);
            start = item;
        }
    }
    cout << count << endl;

    system("pause");
    return 0;
}

7-8求解删数问题(贪心)

输入一个高精度的正整数n,去掉其中任意s个数字后剩下的数字按原左右次序组成一个新的正整数。编程对给定的n和s,寻找一种方案使得剩下的数字组成的新数最小。

输入格式:

输入两个数n(用字符串存储,位数不大于200)和s

输出格式:

输出最小数。

输入样例:

175438 4

输出样例:

13

思路:用字符串存储高精度整数n,若要去除k个字符,那么对字符串进行k次遍历,如果每次遍历的s[i] > s[I + 1]则删除s[i],如果除字符串末尾没有符合的条件来删除字符,即此字符串组成的整数已经为最小最优状态,此时删除末尾字符即可,最后删除前导零即所得最小数。

AC代码:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int i, k;
    string s;
    cin >> s >> k;
    while (k--) {
        for (i = 0; i < s.length(); i++) {
            if (i == s.length() - 1 || s[i + 1] < s[i]) {
                s.erase(i, 1);
                break;
            }
        }
    }
    i = 0;
    while (i < s.length() - 1 && s[i] == '0') {
        s.erase(i, 1);
    }
    cout << s << endl;

    system("pause");
    return 0;
}

7-9跳一跳(模拟)

微信小程序中的跳一跳相信大家都玩过。emmm???只学习不玩游戏?那就吃亏了...好好读题理解吧。
  
简化后的跳一跳规则如下:玩家每次从当前方块跳到下一个方块,如果没有跳到下一个方块上则游戏结束。
  
如果跳到了方块上,但没有跳到方块的中心则获得1分;
跳到方块中心时,若上一次的得分为1分或这是本局游戏的第一次跳跃则此次得分为2分,否则此次得分比上一次得分多两分(即连续跳到方块中心时,总得分将+2,+4,+6,+8...)。
  
现在给出一个人跳一跳的全过程,请你求出他本局游戏的得分(按照题目描述的规则)。

输入格式:

输入包含多个数字,用空格分隔,每个数字都是1,2,0之一,

1表示此次跳跃跳到了方块上但是没有跳到中心,  
2表示此次跳跃跳到了方块上并且跳到了方块中心,  
0表示此次跳跃没有跳到方块上(此时游戏结束)。

对于所有评测用例,输入的数字不超过30个

输出格式:

输出一个整数,为本局游戏的得分(在本题的规则下)。

输入样例:

1 1 2 2 2 1 1 2 2 0

输出样例:

22

思路:简单模拟题,直接上AC代码了

AC代码:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int i, x, item = 1, res = 0;
    vector<int>nums;
    while (cin >> x) {
        nums.push_back(x);
    }
    for (i = 0; nums[i] != 0 && i < nums.size(); i++) {
        if (nums[i] == 1)
            item = 1; 
        else if (i > 0 && nums[i] == 2 && nums[i - 1] == 2)
            item += 2;
        else
            item++;
        res += item;        
    }
    cout << res << endl;

    system("pause");
    return 0;
}

7-10装箱问题(简单贪心)

假设有N项物品,大小分别为s1​、s2​、…、si​、…、sN​,其中si​为满足1≤si​≤100的整数。要把这些物品装入到容量为100的一批箱子(序号1-N)中。装箱方法是:对每项物品, 顺序扫描箱子,把该物品放入足以能够容下它的第一个箱子中。请写一个程序模拟这种装箱过程,并输出每个物品所在的箱子序号,以及放置全部物品所需的箱子数目。

输入格式:

输入第一行给出物品个数N(≤1000);第二行给出N个正整数si​(1≤si​≤100,表示第i项物品的大小)。

输出格式:

按照输入顺序输出每个物品的大小及其所在的箱子序号,每个物品占1行,最后一行输出所需的箱子数目。

输入样例:

8
60 70 80 90 30 40 10 20

输出样例:

60 1
70 2
80 3
90 4
30 1
40 5
10 1
20 2
5

思路:每次都将箱子遍历从头遍历,选取容量>=物品的箱子进行装箱,并更新装箱后的容量即可

AC代码:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int i, j, k, n, count = 0;
    cin >> n;
    vector<int>bag(n, 100);
    vector<pair<int, int>>nums(n, pair<int, int>(0, 0));
    for (i = 0; i < n; i++) {
        cin >> nums[i].first;
    }
    for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) {
            if (bag[j] >= nums[i].first) {
                bag[j] -= nums[i].first;
                nums[i].second = j + 1;
                break;
            }
        }
    }
    for (i = 0; i < n; i++) {
        if (bag[i] == 100)
            break;
        count++;
    }
    for (i = 0; i < n; i++) {
        cout << nums[i].first << " " << nums[i].second << endl;
    }
    cout << count << endl;


    system("pause");
    return 0;
}

7-11Keven的援助(模拟)

现有一个仅由 a,b,c 三个字母构成的字符串 S 。

Keven 认为一个字符串是好的当且仅当这个字符串的前n位字符全是 a ,第 n+1 位到第 2∗n 位全是 b ,第 2∗n+1 到第 3∗n 位全是 c 。

请找出给定的字符串 S 中满足 Keven 要求的子串的 n 的值。

S 的子串 A 定义为字符串 S 从头连续删除若干(可以为0)个字符,从尾连续删除若干(可以为0)个字符,得到的字符串(例如,BAB是ABABA的子串;而BBA不是ABABA的子串)

输入格式:

第一行给出一个正整数 t(1<=t<=100)

之后的t行,每行给出一个字符串 S(1<=∣S∣<=104)

∣S∣ 表示字符串 S 的长度

输出格式:

对每一组输入,在一行中输出最大满足要求的子串长度。

输入样例:

在这里给出一组输入。例如:

3
abc
aab
bcaabbcc

输出样例:

在这里给出相应的输出。例如:

1
0
2

思路:这道题看起来比较难,实际上就是找以abc为基础的最长重复单个字母组成字符串的长度,我们可以进行一次遍历,找到a则把相邻重复的a的个数进行计数,紧接着是b, c,最后取三者数目的最小值即可,注意结尾要判断一下特殊情况,即aaabbcabbccc这种情况

AC代码:

#include<bits/stdc++.h>
using namespace std;
int solve(string s) {
    int i, j, k, n = s.length(), res = 0;
    i = 0;
    while (i < n) {
        int item = 0;
        vector<int>v(3, 0);
        while (s[i] == 'a') {
            v[s[i] - 'a']++;
            i++;
        }
        while (s[i] == 'b') {
            v[s[i] - 'a']++;
            i++;
        }
        while (s[i] == 'c') {
            v[s[i] - 'a']++;
            i++;
        }
        //判断aaabbc以及abbccc这种情况
        if (v[1] <= v[0] && v[1] <= v[2])
            item = min(min(v[0], v[1]), v[2]);
        res = max(res, item);
    }
    return res;
}
int main()
{
    int i, j, k, t;
    cin >> t;
    vector<int>res;
    while (t--) {
        string s;
        cin >> s;
        res.push_back(solve(s));
    }
    for (i = 0; i < res.size(); i++) {
        cout << res[i] << endl;
    }


    system("pause");
    return 0;
}

7-12Swan学院社团招新(贪心)

Swan学院社团招新,招新宣讲会分散在不同时间段,大一新生小花花想知道自己最多能完整的参加多少个招新宣讲会(参加一个招新宣讲会的时候不能中断或离开)。
【问题说明】这个问题是对几个相互竞争的招新宣讲会活动进行调度,它们都要求以独占的方式使用某一公共资源(小花花)。调度的目标是找出一个最大的相互兼容的活动集合。
活动选择问题就是要选择出一个由互相兼容的问题组成的最大子集合。
【温馨提示】应先将所有的活动按照结束时间升序排列,然后再选择可能的时间组合,并求出最大的组合数,使用qsort()排序函数是一个不错的选择。qsort 的函数原型是:
void qsort(voidbase,size_t num,size_t width,int(__cdeclcompare)(const void*,const void*));
功 能: 使用快速排序例程进行排序 头文件:stdlib.h
参数: 1 待排序数组首地址;2 数组中待排序元素数量;3 各元素的占用空间大小;4 指向函数的指针,用于确定排序的顺序

输入格式:

第一行为n,表示有n个招新宣讲会,接下来n行每行两个整数表示开始时间和结束时间,由从招新会第一天0点开始的小时数表示(24小时制)。 n <= 1000 。

输出格式:

最多参加的招聘会个数。

输入样例:

在这里给出一组输入。例如:

 3  
 9 10  
 10 20  
 8 15  

输出样例:

在这里给出相应的输出。例如:

2

思路:

先按照招新宣讲会的开始时间排序,若开始时间相同则按结束时间从小到大排,我们可以维护一个最小堆,堆顶存储每个招新宣讲会的结束时间(其每个宣讲会的开始时间与结束时间之间即一个区间),进行操作

遍历区间:

1):如果判断当前招新宣讲会的开始时间大于堆顶,则这个招新宣讲会可以接在上一个区间后,此时更新堆顶的结束时间和这个组进行的招新宣讲会的个数

2):否则的话需要更新一个新的组(即将此招新宣讲会的结束时间插入堆中,堆顶并不进行更新)

AC代码:

#include<bits/stdc++.h>
using namespace std;
struct cmp {
    bool operator() (pair<int, int>a, pair<int, int>b) {
        return a.first > b.first;
    }    
};
int main()
{
    int i, j, n, maxx = INT_MIN;
    cin >> n;
    vector<vector<int>>nums(n, vector<int>(2, 0));
    for (i = 0; i < n; i++) {
        for (j = 0; j < 2; j++) {
            cin >> nums[i][j];
        }
    }
    sort(nums.begin(), nums.end()), [] (vector<int>a, vector<int>b) {
        return a[1] < b[1];
    };
    priority_queue<int, vector<pair<int, int>>, cmp>Q;
    for (i = 0; i < n; i++) {
        if (!Q.empty() && Q.top().first <= nums[i][0]) {
            int item = Q.top().second;
            Q.push(pair<int, int>(nums[i][1], item + 1));
            Q.pop();
        } else {
            Q.push(pair<int, int>(nums[i][1], 1));
        }
    }
    while (!Q.empty()) {
        maxx = Q.top().second > maxx ? Q.top().second : maxx;
        Q.pop();
    }
    cout << maxx << endl;

    system("pause");
    return 0;
}

7-13区间选点(贪心)

给定N个闭区间[ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。
输出选择的点的最小数量。
位于区间端点上的点也算作区间内。

1<N<1e5

-1e9<ai≤bi≤1e9

输入格式:

第—行包含整数N,表示区间数。
接下来N行,每行包含两个整数a,bi,表示一个区间的两个端点。

输出格式:

一个整数

输入样例:

3
-1 1
2 4
3 5

输出样例:

2

思路:区间按右端点从小到大排序,如果右端点相等的话按左端点从小到大排序,接下来对区间进行遍历,如果当前区间已经包含点,则跳过此区间,否则我们选择当前区间的右端点,并更新右端点

AC代码:

#include<bits/stdc++.h>
using namespace std;
bool cmp(vector<int>a, vector<int>b) {
    if (a[1] == b[1])
        return a[0] < b[0];
    return a[1] < b[1];
}
int main()
{
    int i, j, n, item = INT_MIN, count = 0;
    cin >> n;
    vector<vector<int>>nums(n, vector<int>(2, 0));
    for (i = 0; i < n; i++) {
        for (j = 0; j < 2; j++) {
            cin >> nums[i][j];
        }
    }
    sort(nums.begin(), nums.end(), cmp);
    for (i = 0; i < n; i++) {
        if (item < nums[i][0]) {
            item = nums[i][1];
            count++;
        }
    }
    cout << count << endl;

    system("pause");
    return 0;
}

7-14喷水装置(贪心)

长L米,宽W米的草坪里装有n个浇灌喷头。每个喷头都装在草坪中心线上(离两边各W/2米)。我们知道每个喷头的位置(离草坪中心线左端的距离),以及它能覆盖到的浇灌范围。

请问:如果要同时浇灌整块草坪,最少需要打开多少个喷头?

输入格式:

输入包含若干组测试数据。

第一行一个整数T表示数据组数。

每组数据的第一行是整数n、L和W的值,其中n≤10 000。

接下来的n行,每行包含两个整数,给出一个喷头的位置和浇灌半径。

如图1所示的示意图是样例输入的第一组数据所描述的情况。

图1 

输出格式:

对每组测试数据输出一个数字,表示要浇灌整块草坪所需喷头数目的最小值。如果所有喷头都打开还不能浇灌整块草坪,则输出-1。

输入样例:

3
8 20 2
5 3
4 1
1 2
7 2
10 2
13 3
16 2
19 4
3 10 1
3 5
9 3
6 1
3 10 1
5 3
1 1
9 1

输出样例:

6
2
-1

数据范围与提示:

对于100%的数据,n≤15000。

思路:利用勾股定理我们需要算一个喷水装置真实能覆盖的距离:l = sqrt(r * r – h * h),然后排序,利用贪心思想依次寻找两个边缘交点所能相交的喷水装置,然后向右找所能覆盖的最大半径的喷水装置即可,然后更新右端点

注意:半径r不能作为判断是否覆盖的依据,以下用两个图片进行理解

如果用半径做判断的话

红色区域是覆盖不到的

所以应该用半径与边届焦点到草坪中心线的投影距离l计算

 AC代码:

#include<bits/stdc++.h>
using namespace std;
int solve() {
    int i, j, k, n, l, w;
    cin >> n >> l >> w;
    vector<vector<int>>nums(n, vector<int>(2, 0));
    vector<vector<double>>temp;
    for (i = 0; i < n; i++) {
        cin >> nums[i][0] >> nums[i][1];
    }
    for (i = 0; i < n; i++) {
        if (nums[i][1] > (w * 1.0 / 2)) {
            vector<double>v(2, 0);
            v[0] = nums[i][0] * 1.0 - sqrt((nums[i][1] * nums[i][1]) - ((w * 1.0 / 2) * (w * 1.0 / 2)));
            v[1] = nums[i][0] * 1.0 + sqrt((nums[i][1] * nums[i][1]) - ((w * 1.0 / 2) * (w * 1.0 / 2)));            
            temp.push_back(v);
        }
    }
    sort(temp.begin(), temp.end());
    n = temp.size();
    double item = 0;
    int count = 0;
    i = 0;
    while (i < n && item < l) {
        double right = item;
        while (i < n && temp[i][0] <= right) {
            item = max(item, temp[i][1]);
            i++;
        }
        //cout << "i: "<<i - 1 << endl;
        count++;
        if (item == right && right < l)
            break;
    }
    if (item >= l)
        return count;
    return -1;
}
int main()
{
    int i, j, k, t;
    cin >> t;
    vector<int>res;
    while (t--) {
        res.push_back(solve());
    }
    for (i = 0; i < res.size(); i++) {
        cout << res[i] << endl;
    }

    system("pause");
    return 0;
}

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值