前言
本人是双非科班大一,马上第一次参加蓝桥杯,在参加之前只接触了三个月左右的算法,还是个蒟蒻,斗胆写下一些自己写真题的感受与方法,如有不对请指正。
第十二届
空间
作为B组的签到题,此题主要考基础知识,似乎从来没有这么考过。直接盘算即可
256 * 1024 * 1024 * 8 / 32
卡片
蓝桥杯官网给出的第二题是此题,似乎另有另一道
此题就是个数学问题,直接列几个n盘算找规律即可
- #include <iostream>
- #include <cmath>
- using namespace std;
- int main() {
- long long n;
- cin >> n;
- long long x = 8 * n + 1;
- double s = sqrt(x);
- long long k = (s - 1) / 2;
- if (k * (k + 1) / 2 >= n) {
- cout << k << endl;
- } else {
- cout << k + 1 << endl;
- }
- return 0;
- }
复制代码 直线
此题由于数据较小可以直接暴力罗列所有的情况进行判定,起首需要清除横着的线与竖着的线,即两个点确定的直线的斜率存在且不为0的情况。
为什么要先清除掉呢,主要是因为我们是找到两个点之后,先盘算斜率k,再盘算截距b,假如出现上述情况,就会出现分母为0的情况。
这里的别的一个问题就是由于我们的k与b是double范例的,又因为double范例在进行除法时很容易会出现精度错误导致盘算堕落,所以盘算的b的时候不能直接利用先算出来的k进行盘算,需要转换为b = (y2 * (x2 - x1) - (y2 - y1) * x2) * 1.0 / (x2 - x1)(就是两点式)。
由于会出现重复直线的问题,所以这里利用容器set来进行自动去重
详细代码如下
- #include <iostream>
- #include<set>
- using namespace std;
- set<pair<double,double>>line_set;
- int main()
- {
- double k = 0;
- double b = 0;
- // 请在此输入您的代码
- for(int x1 = 0;x1 < 20;x1 ++)
- for(int y1 = 0;y1 < 21;y1++)
- for(int x2 = 0;x2 < 20;x2 ++)
- for(int y2 = 0;y2 < 21;y2++)
- {
- if(x2 != x1 && y1 != y2)
- {
- k = (y2 - y1) * 1.0 / (x2 - x1);
- b = (y2 * (x2 - x1) - (y2 - y1) * x2) * 1.0 / (x2 - x1);//出现double除法会出现精度问题,
- line_set.insert({k,b});
- }
- }
- printf("%zd",line_set.size() + 20 + 21);
- return 0;
- }
复制代码 货品摆放
题目固然我没有怎么读懂_(:з」∠)_ ,但是我看懂了他举的例子,就是找到给出的n的三个因子然后进行排列,盘算因子我们都知道怎么盘算,但是找到三个因子就可能不知道了。
这时牢记正难则反,我们从反面思考,我们要找到该数的三个因子,使得这三个因子相乘就是该数本身,我们得先预处理一下该数的所有因子,假如我们先锁定该数的两个因子,然后遍历所有的因子找到一个因子使得锁定的两个因子乘上找到的因子是否等于该数即可。
但是这样子相当于要嵌套三层循环,时间复杂度度过高,对他进行一个优化变成两重循环。我们仍然是锁定两个因子,然后假如n%这俩因子相乘等于0的话,那就说明这一组是我们的答案。因为假如n能整除这俩因子的乘积,那就说明整除的商也是n的一个因子,那么我们就找到了三个因子相乘等于n了。
详细代码如下
- #include<iostream>
- #include<algorithm>
- using namespace std;
- long long yue[1000010];
- int main()
- {
- //先把约数提前处理,然后遍历约数相乘
- long long n = 2021041820210418;
- int cnt = 0;
- for (int i = 1; i <= n / i; i++)
- {
- if (n % i == 0)
- {
- yue[++cnt] = i;
- if (i != n / i)
- {
- yue[++cnt] = n / i;
- }
- }
- }
- int ans = 0;
- for(int i = 1;i <= cnt;i++)
- for (int j = 1; j <= cnt; j++)
- {
- if (n % (yue[i] * yue[j]) == 0)
- ans++;
- }
- cout << ans;
- }
复制代码 路径
这个题就是个模版题,幸好学了最短路,固然模版已经忘了_(:з」∠)_也借此复习了一下最短路问题,这个题只要知道最短路的几种算法都可以完成。
这里需要提一下,可能有人不知道最小公倍数怎么盘算,就是先找出最大公约数,然后两数相乘再除以最大公约数即可,最大公约数就是辗转相除法。
详细代码如下
- #include <bits/stdc++.h>
- using namespace std;
- int g[2500][2500];
- int d[2500];
- bool st[2500];
- int gcd(int a,int b)//先找最大公约数
- {
- return b ? gcd(b,a % b) : a;
- }
- int lcm(int a,int b)//再找最小公倍数
- {
- return a * b / gcd(a,b);
- }
- int dijkstra()
- {
- memset(d,0x3f,sizeof d);
- d[1] = 0;
- for(int i = 1;i <= 2021;i++)
- {
- int t = -1;
- for(int j = 1;j <= 2021;j++)
- {
- if(!st[j] && (t == -1 || d[j] < d[t]))
- t = j;
- }//找到距离起点最近的点t
- st[t] = true;
- for(int j = 1;j <= 2021;j++)
- {
- d[j] = min(d[j],d[t] + g[t][j]);
- }
- }
- return d[2021];
- }
- int main()
- {
- for(int i = 1;i <= 2021;i++)
- for(int j = 1;j <= 2021;j++)
- {
- if(i != j)
- {
- if(fabs(i - j) <= 21)
- {
- g[i][j] = g[j][i] = lcm(i,j);
- }
- else
- {
- g[i][j] = g[j][i] = 0x3f3f3f3f;
- }
- }
-
- }//处理数据
- cout << dijkstra();
- return 0;
- }
复制代码 时间显示
蓝桥杯似乎每一届都有关于日期的问题,所有我们得牢记闰年的盘算方法(固然这题没考闰年)即被4整除而且不被100整除大概被400整除。这道题就直接根据题目模拟即可
详细代码如下
- #include <iostream>
- using namespace std;
- typedef long long ll;
- int main()
- {
- // 请在此输入您的代码
- ll n;
- cin >> n;
- n /= 1000;
- int h = n / 3600 % 24;
- n %= 3600;
- int m = n / 60 % 60;
- n %= 60;
- printf("%02d:%02d:%02d",h,m,n);
- return 0;
- }
复制代码 第十三届
九进制转十进制
就直接按照进制转换做即可
- #include <bits/stdc++.h>
- using namespace std;
- int main()
- {
- // 请在此输入您的代码
- cout << 2 * pow(9,0) + 2 * pow(9,1) + 2 * pow(9,3);
- return 0;
- }
复制代码 顺子日期
依然是蓝桥杯喜好考的日期问题,我们只需要判定mmdd是不是顺子就行
详细代码如下
- #include <iostream>
- using namespace std;
- int main()
- {
- // 请在此输入您的代码
- int month[13] ={0,31,28,31,30,31,30,31,31,30,31,30,31};
- //mmdd只需要判断mmdd是不是顺子就行
- int m = 1;
- int ans = 0;
- while(m <= 12)
- {
- int day = month[m];
- for(int i = 1;i < day;i++)
- {
- int a = m / 10,b = m % 10,c = i / 10,d = i % 10;
- if(a == b - 1 && b == c - 1)
- {
- ans++;
- }
- if(b == c - 1 && c == d - 1)
- ans++;
- }
- m++;
- }
- cout << ans;
- return 0;
- }
复制代码 刷题统计
这个问题留意是1e18,所以是需要开long long的(#define int long long的含金量还在上升 ),我没开long long就只过了50%的样例
我们需要先算出要多少周,在盘算剩下的题目需要多少天。
详细代码如下
- #include <iostream>
- using namespace std;
- typedef long long ll;
- int main()
- {
- // 请在此输入您的代码
- ll a,b,n;
- cin >> a >> b >> n;
- ll t = n / (a * 5 + b * 2);//先算周
- ll s = n % (a * 5 + b * 2);//剩下的
- ll ans = t * 7;
- for(int i = 1;s > 0 && i <= 7; i++)
- {
- if(i == 6 || i == 7)
- {
- s -= b;
- ans++;
- }
- else
- {
- s -= a;
- ans++;
- }
- }
- cout << ans;
- return 0;
- }
复制代码 修剪灌木
这个题目固然看着很长,但是在纸上画绘图模拟一遍就出来了,第i棵灌木的最长长度就是i与n-i-1取最大值然后乘上2,乘2是因为往返。
详细代码如下
- #include <bits/stdc++.h>
- using namespace std;
- int main()
- {
- // 请在此输入您的代码
- int n;
- cin >> n;
- for(int i = 0;i < n;i++)
- {
- cout << max(i,n - i - 1) * 2 << endl;
- }
- return 0;
- }
复制代码 第十四届
日期统计
这道题初看我没有任何思绪,以至于我以为需要使用八重循环来遍历所有情况,随后我换了中角度思考,我之前以为要进行八重循环是从给定的数组中找日期,那么为什么不遍历2023年所有的日期然后在数组中寻找呢,所以思绪就是先找到2023年所有的正当日期,2023是个平年,所以不用管2月,然后找到数组中是否有着对应的子序列
详细代码如下
- #include <bits/stdc++.h>
- using namespace std;
- int main()
- {
- // 请在此输入您的代码
- int num[100]={5,6,8,6,9,1,6,1,2,4,9,1,9,8,2,3,6,4,7,7,5,9,5,0,3,8,7,5,8,1,5,8,6,1,8,3,0,3,7,9,2,
- 7,0,5,8,8,5,7,0,9,9,1,9,4,4,6,8,6,3,3,8,5,1,6,3,4,6,7,0,7,8,2,7,6,8,9,5,6,5,6,1,4,0,1,
- 0,0,9,4,8,0,9,1,2,8,5,0,2,5,3,3};
- int month[13] ={0,31,28,31,30,31,30,31,31,30,31,30,31};
- //遍历2023的所有合法日期
- int ans = 0;
- for(int m = 1;m <= 12;m++)
- {
- for(int j = 1;j <= month[m];j++)
- {
- string st = "2023";
- if(m < 10)
- st += "0";
- st += to_string(m);
- if(j < 10)
- st += "0";
- st += to_string(j);
- int k = 0;
- for(int l = 0;l < 100 && k < 8;l++)
- {
- if(num[l] == st[k] - '0')k++;
- }
- if(k == 8)
- ans++;
- }
-
- }
- cout << ans;
- return 0;
- }
复制代码 代码解释
用string来存储是可以方便加上签到0,我们是先找到一个正当日期然后再遍历数组寻找子序列,当我们子序列的正当长度到达8时就说明该日期乐成在数组中找到了。
01串的熵
这个题是找0出现了多少次,所以我们遍历所有0出现的次数,盘算熵即可
详细代码如下
- #include <bits/stdc++.h>
- using namespace std;
- int main()
- {
- // 请在此输入您的代码
- int len = 23333333;
- //暴露枚举所有的0的个数计算信息熵
- for(int i = 1;i <= len / 2;i++)//0的个数比1少
- {
- double ans = 0;
- ans -= 1.0 * i / len * log2(1.0 * i / len) * i ;
- ans -= 1.0 * (len - i) / len * log2(1.0 * (len - i) / len) * (len - i);
- if(fabs(ans -= 11625907.5798) < 1e-4)
- {
- cout << i;
- break;
- }
- }
- return 0;
- }
复制代码 代码解释
迷惑的点就是对数怎么盘算,实在就直接写就行^ _ ^,但这里会卡一个精度问题,同之前的问题,double范例在进行除法盘算时会出现精度问题,导致盘算堕落。
起首需要知道log2这个函数的参数范例是double,返回范例也是double,所以我们需要乘上1.0而且用double来进行担当。
冶炼金属
我这里的方法有些蠢,背面我看别人的题解发现这居然是个数学问题,我的思绪就是先找到最大值,然后让最大值不停自减使得该值是能使条件成立的最小值
详细代码如下
- #include <bits/stdc++.h>
- using namespace std;
- int a[1010][2];
- int Min[1010];
- int main()
- {
- // 请在此输入您的代码
- int n;
- cin >> n;
- for(int i = 0;i < n;i++)
- for(int j = 0;j < 2;j++)
- cin >> a[i][j];
- int ma = 0x3f3f3f3f;
- int mi = 0;
- for(int i = 0;i < n;i++)
- {
- ma = min(ma,a[i][0] / a[i][1]);
- }
- for(int i = 0;i < n;i++)
- {
- mi = ma;
- while(a[i][0] - mi * a[i][1] < mi)
- {
- mi--;
- }
- Min[i] = mi + 1;
- }
- sort(Min,Min + n);
- cout << Min[n - 1] << ' ' << ma;
- return 0;
- }
复制代码 代码解释
这里有几个细节问题,因为我们找到的最大值与最小值是需要使得所有数据均满足的,所以我们的最大值是找的所有最大值中的最小值,最小值是找所有最小值的最大值,我开的Min数组就是存储所有最小值,然后进行排序,输出最大值(因为我当时不会怎么让sort从大到小,所以只能输出最后一个元素)如今会了,也记载一下吧std::sort(nums.begin(), nums.end(), std::greater<int>());需要包含头文件#include <functional>
飞机降落

由于这个问题的N只有10,所以可以直接进行暴力罗列,当时做出来这个题还黑白常开心非常爽的,因为是第一次自己做出来dfs的题目(固然对大佬来说很简单)
思绪就是利用dfs找到所有的排列,我们需要定义一个current记载当前的时间,假如当前飞机在上一架飞机降落之后才到达,那么该飞机就可以直接降落,而且让current加被骗前飞机到达时间与上一架飞机降落时间之差再加被骗前飞机降落所需要的时间。
假如当前飞机是在上一架飞机降落之前就到达了,那么该飞机就需要等候,所以假如当前飞机能等到前一架飞机降落,那么该飞机就可以降落,current加上该飞机降落需要耗费的时间。
大体思绪就是这样,详细代码中仍有一些细节问题。
详细代码如下
- #include <bits/stdc++.h>
- using namespace std;
- int T[21],D[21],L[21];
- bool st[21];
- int n;
- int current;
- bool flag;
- void dfs(int u)
- {
- if (u == n)
- {
- flag = true;
- return;
- }
- for (int i = 0; i < n; i++)
- {
- if (!flag)//提前剪枝
- {
- if (!st[i])//代表当前这个元素没有被使用
- {
- if (current <= T[i])
- {
- st[i] = true;
- int tmp = current;//因为current在回溯过程中会改变,因此需要额外开一个tmp存储当前的current以便回溯
- current += T[i] - tmp + L[i];
- dfs(u + 1);
- st[i] = false;
- current -= T[i] - tmp + L[i];
- }
- else if (current <= T[i] + D[i])
- {
- st[i] = true;
- current += L[i];
- dfs(u + 1);
- st[i] = false;
- current -= L[i];
- }
- else
- {
- continue;//不是return,如果return会多剪枝后面的内容导致循环无法完全进行会遗漏
- }
- }
- }
- }
- }
- int main()
- {
- // 请在此输入您的代码
- int t;
- cin >> t;
- while(t--)
- {
-
- cin >> n;
- memset(T,0,sizeof T);
- memset(D,0,sizeof D);
- memset(L,0,sizeof L);
- memset(st,false,sizeof st);
- flag = false;//这五步初始化,由于是全局变量所以在每一组测试之前都需要初始化
- for(int i = 0;i < n;i++)
- {
- cin >> T[i] >> D[i] >> L[i];
- }
- dfs(0);
- if(!flag)cout << "NO" << endl;//flag表示标记是否已经找到,如果已经找到答案,那么就不需要再找,直接剪枝
- else cout << "YES" << endl;//避免打印多个YES
- }
- return 0;
- }
复制代码 代码解释
起首,因为是多组测试,我们的T,D,L,flag又是全局变量,所以都是需要重新初始化。实现剪枝操作就是通过st数组以及flag,flag表示已经遍历到最后一层,那么就找到了,那么此时我们就不需要遍历其他的组合了,实现了剪枝。在回溯过程中因为我们的current会进行改变,所有需要定义一个tmp存储当前current,这样才可以实现真正的回溯过程。
第十五届
握手问题
直接就是一个小学奥数问题吧,直接让50个人握手减去七个人握手就行
详细代码如下
- #include <iostream>
- using namespace std;
- int main()
- {
- // 请在此输入您的代码
- cout << (49 + 1) * 49 / 2 - (6 + 1) * 6 / 2;
- return 0;
- }
复制代码 小球反弹
这个题我们肯定不能直接盘算,我们知道,小球回到原位置,就需要水平走往返,竖
直走往返,固然小球水平走的时间与竖直走的时间肯定是一样的,我们可以假设水平走p个往返,竖直走q个往返,这样就可以推出公式dx * t= 2 * p * x,dy* t = 2 * q * y,那么两式相除可以得到p/q = y * dx / x * dy,我们令p = y * dx,q = x * dy,但是这里我们需要让p,q都除等上他们的最大公约数,为什么要这么做呢,是因为假如p与q有着公因子的话,那么就说明此时小球并不是第一次到达起点了。
详细代码如下
- #include <bits/stdc++.h>
- using namespace std;
- int gcd(int a,int b)
- {
- return b ? gcd(b,a % b): a;
- }
- int main()
- {
- // 请在此输入您的代码
- //设x上走p个来回,y上走q个来回,因为分解原因,耗费的时间肯定是一样的
- //x上走的距离就是2p*x,dx*t = 2px,dy * t = 2qy,两者相比得到p/q = y * dx / x * dy
- //令p = y*dx,q=x*dy,然后让p,q都/=他们的最大公约数,如果不/=,就说明此时并不是第一次到达起点
- int x = 343720,y = 233333;
- int dx = 15,dy = 17;
- int p = y * dx,q = x * dy;
- int g = gcd(p,q);
- p /= g,q /= g;
- double t = 1.0 * 2 * p * x / dx;
- double ans = t * sqrt(dx* dx + dy * dy);
- printf("%.2lf",ans);
- return 0;
- }
复制代码 好数
我的思绪就是罗列每一个数,判定他是不是好数,所以写了一个get函数来判定这个数是不是好数,我们这里可以优化一下,根据好数的定义,偶数是肯定不是好数的,所以可以特判一下。
详细代码如下
- #include <bits/stdc++.h>
- using namespace std;
- bool get(int n)
- {
- vector<int>num;
- while(n)
- {
- num.push_back(n % 10);
- n /= 10;
- }
- bool flag = false;
- for(int i = 0;i < num.size();i++)
- {
- if(i % 2 == 0)//奇数位
- {
- if(num[i] % 2)
- flag = true;
- else
- flag = false;
- }
- else
- {
- if(num[i] % 2 == 0)
- flag = true;
- else
- flag = false;
- }
- if(!flag)break;
- }
- return flag;
- }
- int main()
- {
- // 请在此输入您的代码
- int n;
- int ans = 0;
- cin >> n;
- for(int i = 1;i <= n;i++)
- {
- if(i % 2 == 0)
- {
- continue;
- }
- if(get(i))
- {
- ans++;
- }
- }
- cout << ans;
- return 0;
- }
复制代码 R格式
我一开始以为这个题不能用高精度乘法做,因为d与另一个数都是很大的,结果背面才发现可以不用把这个2直接乘完而是每次都乘上2。所以这里就是一个高精度的乘法。
详细代码如下
- #include <bits/stdc++.h>
- using namespace std;
- int main()
- {
- string s ;
- int n;
- cin >> n >> s;
- //先让他翻转
- reverse(s.begin(),s.end());
- int pos = s.find('.');
- s.erase(pos,1);
- int a[3050];//这里的a数组必须开大一点
- int len = s.size();
- for(int i = 0;i < len;i++)a[i + 1] = s[i] - '0';
- //高精度乘低精度
- for(int i = 0;i < n;i++)
- {
- for(int j = 1;j <= len;j++)
- {
- a[j] *= 2;//每一位都要乘上2
- }
- for(int j = 1;j <= len;j++)
- {
- if(a[j] >= 10)
- {
- a[j + 1]++;
- a[j] %= 10;
- if(j == len)len++;
- }
- }
- }
- //处理小数点后一位需不需要四舍五入
- if(a[pos] >= 5)a[pos + 1]++;
- for(int i = pos + 1;i <= len;i++)
- {
- if(a[i] >= 10)
- {
- a[i + 1]++;
- a[i] %= 10;
- if(i == len)len++;
- }
- }
- for(int i = len;i >= pos + 1;i--)cout << a[i];
- return 0;
- }
复制代码 代码解释
就是高精度乘法的模版,我们把个位数存储到第一位,这样方便进行存储而且进位,这样的话输出就需要逆序输出,所以先用reverse逆置,这里需要记住一个细节,就是需要判定len是否需要自增,是因为进位问题,这里容易遗忘。
后记
这就是我这四天能做的大概做不了但是看了题解能明白而且自己能做出来的题目了,感觉对于我一个初学算法的小蒟蒻来说,能做出来签到题就谢天谢地了。希望我明日的蓝桥杯可以拿到省三吧。后续会恢复更新C++的学习的。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |