蓝桥杯书的笔记(四:蓝桥云课完结,C++)

蓝桥杯真题精讲之一、二

  • 蓝桥杯真题精讲之一
    • 2020 年蓝桥杯国赛真题--答疑
      • 题目解析
    • 2012 年蓝桥杯省赛真题--鲁卡斯队列
    • 2015 年蓝桥杯模拟真题--金币
    • 最大化股票交易的利润
  • 蓝桥杯真题精讲之二
    • 2021 年蓝桥杯模拟赛真题-谈判
    • 优先队列
    • 2008 年 NOIP 普及组真题-排座椅
    • 总结:

本节实验主要是融汇贯穿前面我们学习过的知识点,通过 4 道蓝桥杯真题进行实战讲解,带领大家学以致用。

蓝桥杯真题精讲之一

2020 年蓝桥杯国赛真题–答疑

题意:

有 n 位同学同时找老师答疑。每位同学都预先估计了自己答疑的时间。
老师可以安排答疑的顺序,同学们要依次进入老师办公室答疑。 一位同学答疑的过程如下:

    首先进入办公室,编号为 i的同学需要 si​ 毫秒的时间。
    然后同学问问题老师解答,编号为 i 的同学需要 ai​ 毫秒的时间。
    答疑完成后,同学很高兴,会在课程群里面发一条消息,需要的时间可 以忽略。
    最后同学收拾东西离开办公室,需要 ei​ 毫秒的时间。一般需要 10 秒、20 秒或 30 秒,即 ei​ 取值为 10002000030000。

一位同学离开办公室后,紧接着下一位同学就可以进入办公室了。
答疑从 0 时刻开始。老师想合理的安排答疑的顺序,使得同学们在课程群 里面发消息的时刻之和最小。

输入输出:

输入描述:
输入第一行包含一个整数 n,表示同学的数量。

接下来 n 行,描述每位同学的时间。其中第 i 行包含三个整数 si​, ai​, ei​,意义如上所述。

其中有 ,1≤n≤10001≤si≤600001≤ai≤106,ei∈10000,20000,30000。即 ei​ 一定是 100002000030000之一。

输入样例:
3
10000 10000 10000
20000 50000 20000
30000 20000 30000

输出描述:
输出一个整数,表示同学们在课程群里面发消息的时刻之和最小是多少。

输出样例:
280000

题目解析

本题是一个贪心问题,要想使得所有的时刻之和最小,就要使得每一个时刻尽可能少,根据题目我们可以得到每位同学的时刻=每位同学的等待时间+进门时间+答疑时间。

由于答疑时间是已知的,要使得每位同学的时刻最小,那么就要使得每位同学的等待时间最小。如何使得等待时间最小的呢?

如果这么考虑这道题是做不出来的,我们应该考虑的是使得每位同学等待的时间和最小。

每位同学的时刻 = 每位同学的等待时间 + 进门时间 + 答疑时间
             = 前一位同学的等待时间 + 前一位同学的进门时间 + 前一位同学的答疑时间 + 前一位同学的收拾东西的时间 + 进门时间 + 答疑时间

蓝桥杯书的笔记(四:蓝桥云课完结,C++)_第1张图片
使用贪心算法后,我们可以得出结论是,每位同学的等待时间(前一位同学的等待时间 + 前一位同学的进门时间 + 答疑时间 + 前一位同学的收拾东西的时间)最小。

但是如果相同的时候,两者前后关系是什么?

  • 因为是答疑结束后就发消息,而不是出门之后发消息,所以两者存在前后关系,谁先答疑结束谁就先执行。
  • 我们还要进一步考虑,如果进门时间+答疑时间相同,即答疑同时结束,既然答疑同时结束,那么谁先谁后结果是相同的,所以不用继续考虑,到此,贪心策略完整的生成。

即:我们选取最小的进门时间+答疑时间+收拾东西时间之和最小的人在前,且当进门时间 + 答疑时间 + 收拾东西时间的和相同时,选择最小的进门时间 + 答疑时间。

代码:

#include 
#include 
#include 
#include 

using namespace std;

const int N = 1010;
int n;

struct Stu
{
    int inD;
    //进门所需时间

    int answQ;
     //答疑所需时间

    int outD;
    //收拾东西所需时间

    int sum1;
    //贪心准则1=进门时间+答疑时间+收拾东西时间

    int sum2;
    //贪心准则2=进门时间+答疑时间+收拾东西时间
} stu[N];


//贪心准则
bool cmp(Stu st1, Stu st2)
{
    if(st1.sum1 != st2.sum1)    return st1.sum1 < st2.sum1;
    else return st1.sum2 < st2.sum2;
}

int main()
{
    //输入数据
    scanf("%d", &n);

    for(int i = 0; i < n; i ++ )
    {
        scanf("%d%d%d", &stu[i].inD, &stu[i].answQ, &stu[i].outD);

        //标准生成
        stu[i].sum1= stu[i].inD + stu[i].answQ+stu[i].outD;
        stu[i].sum2 =  stu[i].inD + stu[i].answQ;
    }

    //贪心过程及结果计算
    sort(stu, stu + n, cmp);

    long long res = 0, t = 0;

    for(int i = 0; i < n; i ++ )
    {
        t += stu[i].sum2;

        res += t;

        t += stu[i].outD;

    }

    cout << res << endl;

    return 0;
}

2012 年蓝桥杯省赛真题–鲁卡斯队列

这道题是基于前缀和的模拟题,我们按照要求进行模拟即可。
题意:

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

黄金分割数 0.618 与美学有重要的关系。舞台上报幕员所站的位置大约就是舞台宽度的 0.618 处,墙上的画像一般也挂在房间高度的 0.618 处,甚至股票的波动据说也能找到 0.618 的影子....

黄金分割数是个无理数,也就是无法表示为两个整数的比值。0.618 只是它的近似值,其真值可以通过对 5 开方减去 1 再除以 2 来获得,我们取它的一个较精确的近似值:0.618034 。

有趣的是,一些简单的数列中也会包含这个无理数,这很令数学家震惊!
1 3 4 7 11 18 29 47....  称为“鲁卡斯队列”。它后面的每一个项都是前边两项的和。

如果观察前后两项的比值,即:1\3 3\4 4\7 7\11 11\18...会发现它越来越接近于黄金分割数!

你的任务就是计算出从哪一项开始,这个比值四舍五入后已经达到了与 0.618034 一致的精度。

请写出该比值。格式是:分子/分母。比如:29/47
输入描述:
无

输入样例:
输出描述:
输出一个整数,表示该比值。格式是:分子/分母。比如:29/4729/4729/47。

输出样例:
#include
#include
#include
using namespace std;
double a[51] = { 1,3 };
void init()
{
    for (int i = 2; i < 50; i++)
    {
        a[i] = a[i - 1] + a[i - 2];
    }
}

string comp()
{
    for (int i = 0; i < 50; i++)
    {
        double b = a[i] / a[i + 1];
        if (abs(b - 0.618034) <= 0.000001)
        {
            stringstream s1;
            s1 << a[i] << "/" << a[i + 1];

            string s;
            s1>>s;
            return s;
        }
    }
}

int main()
{
    init();
    string ans=comp();

    cout<<ans<<endl;
    return 0;
}

stringstream 类:

这里给出的 C++ 代码描述,会写的相对繁琐一些,目的是为了给大家讲一种新的字符串使用技巧。C++ stringstream 类是一种十分有用的类,特别是当我们需要在程序中使用字符串和数字数据互相转换的时候。

要想在程序中使用 stringstream 类,我们需要在源程序文件中包含头文件include。

stringstream 对象的使用方法与 cout 对象和 cin 的使用方法基本相同。>> 这个符号就很形象,比如:

cin>>a 可以理解为将数据流流入到 a 中
cout< 可能对于底层的描述不太恰当,但是大家记住 >> 指向谁,则是将数据给到谁,stringstream 当成 cin cout 用即可。在我上面给出的代码中,大家可以看到我还将数据还进行了转化处理,在 C++ 中数据类型的转化使用 stringstream 也是不错的选择。

2015 年蓝桥杯模拟真题–金币

这道题是前缀和的变种,变种的方式可以通过模拟解决,时间复杂度为 O(N2),但是题目的范围为 1e4,不超时方法可行。
题意:

国王将金币作为工资,发放给忠诚的骑士。

第一天,骑士收到一枚金币;
之后两天(第二天和第三天),每天收到两枚金币;
之后三天(第四、五、六天),每天收到三枚金币;
之后四天(第七、八、九、十天),每天收到四枚金币......;

这种工资发放模式会一直这样延续下去:当连续 N 天每天收到 N 枚金币后,骑士会在之后的连续 N+1 天里,每天收到 N+1 枚金币。

请计算在前 K 天里,骑士一共获得了多少金币。
输入描述:
输入只有 1 行,包含一个正整数 K (1≤K≤104),表示发放金币的天数。

输入样例:
6
输出描述:
输出只有 1 行,包含一个正整数,即骑士收到的金币数。

输出样例:
1000
#include
#include
#include
using namespace std;


int comp(int n)
{

    int sum = 0;
    int day = 0;

    for (int i = 1; i <= n; i++)
    {

        for (int j = 0; j < i; j++)
        {

            sum += i;
            day += 1;

            if (day == n)
                return sum;

        }
    }
    return sum;
}
int main()
{

    int n;

    cin>>n;
    int ans=comp(n);
    cout<<ans;

}

最大化股票交易的利润

这个题目获得最大利润的方式,就是存在 A,B 两天,A 在 B 前一天,使得 B—A 的值最大。
题意:

实现一个算法寻找最大化股票交易利润的策略。介绍如下:

- 股票价格每天都在变化,以数组的索引表示交易日,以数组的元素表示每天的股票价格。

- 可以通过买入和卖出获得利润。一天只能进行一次买入或卖出操作,一次买入加卖出操作称为一次交易次数。

- 你只能交易一次,求使得利润最大的交易策略。
输入描述:

第一行为数字 N,表示共有 N 天。

第二行为 N 个数字 Ai​,表示每天的股票价格。

其中,1≤N,Ai≤1e4。

输入样例:

8
2 5 6 1 4 3 1 3
输出描述:

输出一行,为交易一次的最大利润(有可能利润为负)。

输出样例:

4
#include 
#include 
using namespace std;
int main()
{

    int a[100005];
    int n;

    cin>>n;

    int mini=-0x3f3f3f3f;//一个常用的极小值


    for(int i=0; i<n; i++)
    {
        cin>>a[i];
    }

    for(int i=0; i<n-1; i++)
    {
        for(int j=i+1; j<n; j++)
        {

            int t=a[j]-a[i];

            if(mini<t)
            {
                mini=t;
            }
        }
    }
    cout<<mini;

    return 0;
}

蓝桥杯真题精讲之二

2021 年蓝桥杯模拟赛真题-谈判

题意:

题目描述

在很久很久以前,有 n 个部落居住在平原上,依次编号为 1 到 n。第 i 个部落的人数为 ti​。

有一年发生了灾荒。年轻的政治家小蓝想要说服所有部落一同应对灾荒,他能通过谈判来说服部落进行联合。

每次谈判,小蓝只能邀请两个部落参加,花费的金币数量为两个部落的人数之和,谈判的效果是两个部落联合成一个部落(人数为原来两个部落的人数之和)。
输入的第一行包含一个整数 nnn,表示部落的数量。

第二行包含 nnn 个正整数,依次表示每个部落的人数。

其中,1≤n≤10001≤ti≤1e4
输出一个整数,表示最小花费。
示例 1
输入
4
9 1 3 5

输出
31

这题是一个贪心问题,要想使得花费金额之和最小,就要使得每一次的花费尽可能少。

根据题目我们可以得到,合成的新部落的花费=人数为原来两个部落的人数之和。

如何使得等待时间最小的呢?

如果这么考虑这道题是做不出来的,应该考虑的是使得每位同学等待的时间和最小。我们回忆一下答疑拿到题目:

第一位同学等待时间 S1=T1=0 -----1)

第二位同学等待时间 S2=T1+T2=T2 -----2)

第三位同学等待时间 S3=T1+T2+T3=T2+T3 -----3......

第 N 位同学等待时间 Sn=T1+T2+T3+T4+T5+...+Tn-1 -----(n)

将 1 到 n-1 式带入 n 式得

Sn=T1*n+T2\*(n-1)+T3\*(n-1)+....+Tn

由此可知前面的系数是最大的,所以要使前面的时间最小。

于是得出了贪心策略进而解决问题。

此时贪心的除了结论是每位同学的等待时间(前一位同学的等待时间+前一位同学的进门时间+答疑时间+前一位同学的收拾东西的时间)最小。

一组样例:

4

3 4 5 6

如果按照顺序组合的话:

3+4=7

7 5 6

7+5=14

14 6

14+6=20

最终花费:7+14+20=41

其实答案应该是:

3 4 5 6

3+4=7

7 5 6

再次排序

5 6 7

5+6=11

11 7

11+7=18

最终花费为 7+11+18=36

所以这里贪心原则是维护最小的值,但是每次都会进行更新,每次更新后就要重新排序,时间复杂度是 Nlog(n) 的复杂度,这里是可以通过的,当然我们也可以使用优先队列解题。

优先队列的使用方式跟队列是一模一样的,优先队列会自动进行排序而已。

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出(first in, largest out)的行为特征。通常采用堆数据结构来实现。

简单的多次排序做法:

#include 
#include 
#include 
using namespace std;
int main()
{
    int n;
    cin>>n;
    int cnt=0,k;

    vector<int> t;

    for(int i=0; i<n; i++)
    {
        cin>>k;
        t.push_back(k);
    }
    while(t.size()>1)
    {
        //排序
        sort(t.begin(),t.end());

        //取出前两个值
        int k=t[0]+t[1];

        //答案求和
        cnt+=k;

        //删除前两个利用过的值
        t.erase(t.begin());
        t.erase(t.begin());

        //将产生的新值加入集合
        t.push_back(k);
    }
    cout<<cnt<<endl;
    return 0;
}

优先队列做法:

#include
#include
using namespace std;
int main()
{
    int n,cnt=0;
    priority_queue<int>pq; //默认是大顶堆,从大到小排序
    cin>>n;
    for(int i=0; i<n; ++i)
    {
        int a;
        cin>>a;
        pq.push(-a); //存入负值,从小大排序
    }

    if(pq.size()==1)
    {
        cout<<-pq.top();
    }
    else
    {
        while(pq.size()!=1)
        {
            int x=pq.top();
            pq.pop();

            int y=pq.top();
            pq.pop();

            pq.push(x+y);
            cnt+=-x-y;
        }
        cout<<cnt<<endl;
    }
    return 0;
}

优先队列

首先要包含头文件 #include,他和 queue 不同的就在于我们可以自定义其中数据的优先级,让优先级高的排在队列前面,优先出队。

优先队列具有队列的所有特性,包括队列的基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的。

蓝桥杯书的笔记(四:蓝桥云课完结,C++)_第2张图片

2008 年 NOIP 普及组真题-排座椅

题意:

上课的时候总有一些同学和前后左右的人交头接耳,这是令小学班主任十分头疼的一件事情。

不过,班主任小雪发现了一些有趣的现象,当同学们的座次确定下来之后,只有有限的 D 对同学上课时会交头接耳。

同学们在教室中坐成了 M 行 N 列,坐在第 i 行第 j 列的同学的位置是(i,j),为了方便同学们进出,在教室中设置了 K 条横向的通道,L 条纵向的通道。

于是,聪明的小雪想到了一个办法,或许可以减少上课时学生交头接耳的问题:她打算重新摆放桌椅,改变同学们桌椅间通道的位置,因为如果一条通道隔开了两个会交头接耳的同学,那么他们就不会交头接耳了。

请你帮忙给小雪编写一个程序,给出最好的通道划分方案。在该方案下,上课时交头接耳的学生对数最少。

输入:

输入第一行,有 5 各用空格隔开的整数,分别是 M,N,K,L,D(2≤N,M≤10000≤K<M,0≤L<N,D≤2000)。

接下来 D 行,每行有 4 个用空格隔开的整数,第 i 行的 4 个整数 Xi,Yi,Pi,Qi,表示坐在位置 (Xi,Yi)(Pi,Qi) 的两个同学会交头接耳(输入保证他们前后相邻或者左右相邻)。

输入数据保证最优方案的唯一性。

输出:

输出共两行。

第一行包含 K 个整数,a1,a2,⋯aK,表示第 a1a_1a1​ 行和 a1+1a_1+1a1​+1 行之间、第 a2 行和第 a2+1行之间、…、第 aK​ 行和第 aK+1 行之间要开辟通道,其中 ai<ai+1,每两个整数之间用空格隔开(行尾没有空格)。

第二行包含 L 个整数,b1,b2,⋯bk​,表示第 b1​ 列和 b1+1 列之间、第 b2 列和第 b2+1 列之间、…、第 bL​ 列和第 bL+1 列之间要开辟通道,其中 bi<bi+1,每两个整数之间用空格隔开(行尾没有空格)。
示例 1

输入

4 5 1 2 3
4 2 4 3
2 3 3 3
2 5 2 4
 
输出

2
2 4

蓝桥杯书的笔记(四:蓝桥云课完结,C++)_第3张图片
蓝桥杯书的笔记(四:蓝桥云课完结,C++)_第4张图片
所以输出需划分的通道时,要先将通道按编号由小到大排序后再输出 。

#include
#include
#include
 
using namespace std;

const int MAXN= 1001;
int x[MAXN]; //横坐标桶
int y[MAXN]; //纵坐标桶

int c[MAXN];
int o[MAXN];
 
int main()
{

 
    int M, N, K, L, D;
    cin >> M >> N >> K >> L >> D;
    int xi, yi, pi, qi;

    while (D--)
    {
        cin >> xi >> yi >> pi >> qi;

        if (xi == pi) //横坐标相同
        {
            y[min(yi, qi)]++;
        }
        else //纵坐标相同
        {
            x[min(xi, pi)]++;
        }
    }

    //两重循环找出前K大的横坐标值
    for (int i = 1; i <= K; i++)
    {
        int maxn = -1;
        int p;
        for (int j = 1; j < M; j++)
        {
            if (x[j]>maxn)
            {
                maxn = x[j];
                p = j;
            }
        }
        x[p] = 0;
        c[p]++;
    }
    //两重循环找出前L大的横坐标值
    for (int i = 1; i <= L; i++)
    {
        int maxn = -1;
        int p;
        for (int j = 1; j < N; j++)
        {
            if (y[j]>maxn)
            {
                maxn = y[j];
                p = j;
            }
        }
        y[p] = 0;
        o[p]++;
    }
    
    for (int i = 0; i<MAXN; i++)
    {
        if (c[i])
            printf("%d ", i);
    }
    printf("\n");
    for (int i = 0; i<MAXN; i++)
    {
        if (o[i])
            printf("%d ", i);
    }
    return 0;
}

总结:

可见在真正的竞赛中,像这样各种知识的混着出题,才是常见的。单一知识点出题,我们称作签到题,就是所有人都会做的。当然在蓝桥杯省赛中,能够将前面的知识学会学好,省二是没有问题,想要冲击更高的奖项,基础篇只是打好了基础,后面的课程会带你认识更多的算法,体验算法之美。

原网址:https://www.lanqiao.cn/courses/3993/learning/?id=248906

你可能感兴趣的:(蓝桥杯+力扣,蓝桥杯,c++,算法)