试题 A: 日期统计(填空题)
小蓝现在有一个长度为 100 的数组,数组中的每个元素的值都在 0 到 9 的 范围之内。数组中的元素从左至右如下所示:
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
现在他想要从这个数组中寻找一些满足以下条件的子序列:
- 子序列的长度为 8;
- 这个子序列可以按照下标顺序组成一个 yyyymmdd 格式的日期,并且要求这个日期是 2023 年中的某一天的日期,例如 20230902,231223。 yyyy 表示年份,mm 表示月份,dd 表示天数,当月份或者天数的长度只有一位时需要一个前导零补充。请你帮小蓝计算下按上述条件一共能找到多少个不同 的 2023 年的日期。对于相同的日期你只需要统计一次即可。
思想
像枚举给定数组内所有合法日期是有个非常大的量,所以我们去枚举2023年所有合法日期,再去给定数组内找,如果找到了,那么就用计数器统计一下。
code
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
//枚举出所有的年月日
string mon[] = { "01","02","03","04","05","06","07","08","09","10","11","12" };
string day[] = { "01","02","03","04","05","06","07","08","09","10",
"11","12","13","14","15","16","17","18","19","20",
"21","22","23","24","25","26","27","28","29","30","31" }; //默认31天
int md[]={31,28,31,30,31,30,31,31,30,31,30,31}; //1 3 5 7 9 31天,默认为平年
//从所有年月日中筛出合法日期
bool check(string p)
{
if(p.substr(0, 4)!="2023") return false; //从输入数组里找合法年份(2024)
string mon = p.substr(4, 2); //从输入数组里找合法月份 (1~12)
string day = p.substr(6, 2); //从输入数组里找合法天数
int m = (mon[0]-'0')*10+(mon[1]-'0');
int d = (day[0]-'0')*10+(day[1]-'0');
//枚举所有合法日期
if (m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10 || m == 12)
{
if (d >= 1 && d <= 31)return true;
}
else if (m == 2)
{
if (d >= 1 && d <= 28)return true;
}
else if (m==4 || m == 6 || m == 9 || m == 11)
{
if (d >= 1 && d <= 30)return true;
}
return false;
}
//从输入数组里找合法日期
bool check(string p, string N) //比较 输入的日期 合法日期
{
int j = 0;
for (int i = 0; i < p.size();++i) //枚举输入的日期
{
if (j == N.size())return true; //如果j和合法日期位数相等(8位)就退出,不再比较
if (p[i] == N[j])j++; //如果日期相等,那么就j++
}
return j == N.size();
}
string p, s;
int ans, n;
int main()
{
while (cin >> s)
{
p += s;
}
n = s.size();
string t = "2023"; //获取头部
for (int i = 0; i < 12; i++) //遍历月份
{
for (int j = 0; j < md[i]; j++) //遍历天数
{
string N = t + mon[i] + day[j]; //存一个日期
if (check(p, N)) //检查日期是否合法
{
ans++; //合法就用计数器统计一下
}
}
}
cout << ans << endl;
return 0;
}
思想
这道题就是让我们通过熵的定义来求S当中0和1的占比。
拿给的样例举例子,当S=100时,此时1的占比是 ,0的占比是
用熵公式表示为:
S是一个只由0和1组成的数,也就是除了0就是1,我们把0的占比求出来1的占比也就求出来了。推导过程如下:
code
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
const int total = 23333333;
const double H = 11625907.5798;
int main() {
int l = 0, r = total / 2;
while (l < r) {
int mid = (l + r) >> 1;
double ans = 0;
ans -= 1.0 * mid * mid / total * log2(1.0 * mid / total);
ans -= 1.0 * (total - mid) * (total - mid) / total * log2(1.0 * (total - mid) / total);
if (abs(ans - H) < 1e-4) {
cout << mid << endl;
return 0;
}
if (ans > H) {
r = mid;
} else {
l = mid + 1;
}
}
return 0;
}
C: 冶炼金属(10分)
问题描述 小蓝有一个神奇的炉子用于将普通金属O 冶炼成为一种特殊金属X。这个炉子有一个称作转换率的属性V ,V 是一个正整数,这意味着消耗V 个普通金属O 恰好可以冶炼出一个特殊金属X ,当普通金属O 的数目不足V时,无法继续冶炼。
现在给出了N 条冶炼记录,每条记录中包含两个整数A和B,这表示本次投入了A个普通金属O ,最终冶炼出了B 个特殊金属X。每条记录都是独立的,这意味着上一次没消耗完的普通金属O 不会累加到下一次的冶炼当中。
根据这N 条冶炼记录,请你推测出转换率V 的最小值和最大值分别可能是多少,题目保证评测数据不存在无解的情况。
输入格式
第一行一个整数N ,表示冶炼记录的数目。 接下来输入N行,每行两个整数A 、B ,含义如题目所述。
输出格式
输出两个整数,分别表示V可能的最小值和最大值,中间用空格分开。
样例输入
3
75 3
53 2
59 2
样例输出
20 25
数据范围
对于30 % 30%30% 的评测用例,
对于60 % 60%60% 的评测用例,
。
对于100 % 100%100% 的评测用例,
思想
枚举所有普通金属可以冶炼成特殊金属的最少个数和最大个数,取它们的共同交集,
拿题目样例举例:
第一行:
75个普通金属o,冶炼率:3
75/3=25,也就是最多可以冶炼出25个特殊金属x。
现在我们来求最少可以冶炼出多少个特殊金属。
我们把冶炼率设为不可达,即4,75/4=18
也就是说18是非法的,当冶炼率为3的时候最少冶炼出19个特殊金属。
第二行
53个普通金属o,冶炼率2。
53/2=26,最多可以冶炼出26个特殊金属x
将冶炼率设为不可达数3,53/3=17,17为不可达数。
冶炼率为3的时候最少可以冶炼出18个特殊金属x
第三行
59个普通金属o,冶炼率为2.
59/2=29,最多可以冶炼出29个特殊金属。
将冶炼率设为不可达数3,59/3=19,19为不可达
所有冶炼率为2的时候,最少特殊金属数是 20;
答案
然后我们把求出来的所有范围做一个交集,发现相交的部分是[20,25],这就是答案:
上面是我们手算的,接下来我们将用代码进行计算:
code
我刚开始是这样写的:
#include<iostream>
#include<cmath>
#define INT_MAX 2147483647
using namespace std;
int main()
{
int A,B;
int n=0;cin>>n;
int max_n=0,min_n=INT_MAX;
while(n--)
{
cin>>A>>B;
max_n=max(max_n,A/B);
min_n=min(min_n,A/(B+1)+1);
}
cout<<min_n<<" "<<max_n<<endl;
}
这样确是求出可以冶炼出的最大最少特殊金属个数,但是这道题是让我们所有交集中冶炼的特殊金属的最大最小个数,所以需要改代码,如下:
#include<iostream>
#include<cmath>
#define INT_MAX 2147483647
using namespace std;
int main()
{
int A,B;
int n=0;cin>>n;
int max_n=INT_MAX,min_n=0;
while(n--)
{
cin>>A>>B;
min_n=max(min_n,A/(B+1)+1);
max_n=min(max_n,A/B);
}
cout<<"最小值:"<<min_n<<" "<<"最大值:"<<max_n<<endl;
}
飞机降落 (10分)
蓝桥杯2023年第十四届省赛真题-飞机降落 - C语言网 (dotcpp.com)
每个飞机都可以在【T, T + L】区间内降落,最晚可以在【T + D, T + L + D】区间内降落。
降落需要花费 L 个时间,也就是说要在【T, T + D + L】这个区间选择一个长度为 L 的区间(可以自己在纸上画个图,就很清楚了)。
总共 N 个飞机,所以我们需要在每架飞机各自的【T, T + D + L】 区间内选择出合适的长度为 L 的区间,使得这 N 个长度为 L 的区间互相不重叠,就可以降落,如果有重叠,就无法降落。
因为题目数据范围很小,所以可以用DFS:
记录两个状态:
一是当前一共降落了几架飞机。
二是上一架飞机降落完毕的时间点。
对于上一架飞机,开始降落的时间点能早就早,但是不能比它自己的 “上一架” 的降落完毕的时间早
代码如下:
如果已经降落了10架飞机,就返回1,证明可以全部降落;
否则需要找一架飞机,满足这样的条件:
还没有降落:! book[i]
最晚开始降落的时间比上一架飞机的降落完毕的时间要靠后:q[i].a+q[i].b>=last
code
#include<iostream>
#include<cstring>
using namespace std;
int t,n;
struct Plane
{
int T;
int D;
int L;
}plane[11]; //T最多为10,也就是最多10个飞机
bool vis[11];
bool dfs(int num,int last)
{
if(num==n)return 1; //所有飞机全部遍历完了,就结束递归
for(int i=1;i<=n;++i)
{
if(!vis[i]&&plane[i].T+plane[i].D>=last) //当前飞机降落时间在上个飞机最晚降落时间内是非法情况
{
vis[i]=1; //标记当前飞机降落实际为非法情况
if(dfs(num+1,max(last,plane[i].T)+plane[i].L)) //num+1跳到下个飞机,max求最晚降落时间
{
return 1; //合法情况
}
vis[i]=0; //合法情况做标记
}
}
return 0;
}
int main()
{
cin>>t;
while(t--)
{
memset(vis,0,sizeof vis); //每组样例都要恢复标记
cin>>n;
for(int i=1;i<=n;++i)
{
cin>>plane[i].T>>plane[i].D>>plane[i].L;
}
if(dfs(0,0)) cout<<"YES\n";
else cout<<"NO\n";
}
return 0;
}
E 接龙序列
P9242 [蓝桥杯 2023 省 B] 接龙数列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
code
#include <bits/stdc++.h>
using namespace std;
int n, f, b, ans;
int dp[15];
string a; // 用字符串存储更简单
int main() {
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a;
f = a.front() - '0';
b = a.back() - '0';
dp[b] = max(dp[b], dp[f] + 1); // 状态转移方程
}
for(int i = 0; i <= 9; i++)
ans = max(ans, dp[i]);
cout << n - ans << endl;
return 0;
}
F岛屿个数
蓝桥杯2023年第十四届省赛真题-岛屿个数 - C语言网 (dotcpp.com)
思想
正常的洪水灌溉思想,就是正常从(0,0)位置开始搜,但是这里还要再写个bfs()来搜子岛屿的个数
子岛屿的求法
只要被围住的岛屿可以和外面的海连通那么这个子岛屿就是个独立的岛屿,判断是否子岛屿和外面的海连通我们要从八个方向去看,上下左右对角线:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<queue>
#include<cstring>
#include<string>
using namespace std;
typedef pair<int, int> PII;
const int N = 55;
string mape[N];
bool vis[N][N];
bool used[N][N];
int m, n;
int dx[8] = { 0,0,1,-1,1,-1,1,-1 };
int dy[8] = { 1,-1,0,0,1,1,-1,-1 };
void bfs(int x, int y)
{
queue<PII> q;
//起始地点,从左上角开始
//把起始地点加入到队列当中
q.push({ x,y });
//memset(vis, 0, sizeof vis); //全部初始化为未走过
vis[0][0] = 1; //起始地点标记为走过
while (!q.empty())
{
//弹出队头元素
PII t = q.front();
q.pop();
//把其他元素加入到队尾
//开始染色 ,往四个方向染色
for (int i = 0; i < 4; i++)
{
int x = t.first + dx[i], y = t.second + dy[i];
if (x<0 || x>=m || y<0 || y>=n || vis[x][y] || mape[x][y] == '0')continue; //越界或者是已经遍历过了或者是海水就不能走
q.push({ x,y });
vis[x][y] = 1; //当前位置标记为走过了
}
}
}
bool bfs_out(int x, int y)
{
for (int i=0;i<m;i++)
{
for (int j=0;j<n;j++)
{
used[i][j] = 0;
}
}
queue<PII> q;
q.push({ x,y });
used[0][0] = 1; //标记为已经走过了
while (!q.empty())
{
PII t = q.front();
q.pop();
if (t.first == 0 || t.first == m-1 || t.second == 0 || t.second == n-1 )return true; //能走到边界就能逃出去
//枚举八个方向
for (int i = 0; i < 8; i++)
{
int x = t.first + dx[i], y = t.second + dy[i];
if (x<0 || x>=m || y<0 || y>=n || used[x][y] || mape[x][y] == '1')continue; //这里要找海水
q.push({ x,y });
used[x][y] = 1; //标记为走过了
}
}
return false;
}
void sovel()
{
cin >> m >> n;
for (int i = 0; i < m; i++)
{
cin >> mape[i];
for (int j = 0; j < n; j++)
{
vis[i][j] = 0;
}
}
int ans = 0;
for (int i = 0; i < m; i++)
for (int j = 0; j < n; ++j)
if (!vis[i][j] && mape[i][j] == '1') //如果当前位置没有被遍历过,并且是岛屿就进行染色
{
bfs(i, j);
if (bfs_out(i, j)) //能够逃出去说明就多了个岛屿
++ans;
}
cout << ans << endl;
}
int main()
{
int t = 0; cin >> t;
while(t--)
sovel();
}
我刚开始是在bfs()函数内部用memset(vis,0,sizeof vis)来对vis进行初始化,运行结果为2 6 正确答案为1 ,3。
实际上应该在每次调用bfs()的时候都要对vis()清空,所以vis()的清空我又放在了外面,结果就对了。
子串简写
蓝桥杯2023年第十四届省赛真题-子串简写 - C语言网 (dotcpp.com)
根据题目要求,我们要查找的范围是不能小于K的,也就是c2要距离c1k个位置。拿题目样例举例子:
我们枚举第一个c1的位置,那么c2就必须在下标为3的位置,这个位置就是0+k-1。
我们把c1在0位置,所有满足大于k-1位置的c2枚举出来
一共有3个c2是合法的:
接下来枚举第2个c1:
我们发现c1和c2之间的距离没有超过k,因此也是非法情况,我们往后k-1(下标从0开始)个位置开始找b: 发现有两个c2满足条件
接下来枚举第三个c1,再往后k-1个位置开始找b: 发现只有一个c2满足条件:
答案
满足条件的总共有 3+2+1=6
总结
我们每次去枚举c1,每枚举一个c1,就从距离c1 k-1个位置开始找c2,找到一个c2就统计一下,用前缀和求出所有方案。
code
#include<iostream>
using namespace std;
typedef long long LL;
int K;
char c1,c2;
string S;
int main()
{
cin>>K>>S>>c1>>c2;
int sum_c1=0;
LL ans=0;
int n=S.size();
for(int i=K-1,j=0;i<n;i++,j++) //i=k-1,也就是我们枚举c2的时候要从k-1开始枚举,j=0,也就是c1可以从0位置开始枚举
{
if(S[j]==c1)++sum_c1; //每枚举一个c1就统计一下
if(S[i]==c2)ans+=sum_c1; //如果枚举到了一个c2,就把前面统计的全部累加起来
}
cout<<ans<<endl;
return 0;
}
dp做法
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 500010;
int f[N];
int k;
string str;
char c1, c2;
ll ans;
int main()
{
cin >> k;
cin >> str;
cin >> c1 >> c2;
int len = str.size();
str = " " + str;
for(int i = len;i >= 1;i--)
{
f[i] = f[i + 1];
if(str[i] == c2) f[i] = f[i + 1] + 1;
}
for(int i = 1;i + k - 1 <= len;i++)
{
if(str[i] == c1) ans += f[i + k - 1];
}
cout << ans << endl;
return 0;
}