【2023蓝桥真题】C++ C组题解
好了,我们开始
目录
飞机降落
N 架飞机准备降落到某个只有一条跑道的机场。其中第 i 架飞机在 Ti 时刻到达机场上空,到达时它的剩余油料还可以继续盘旋 Di 个单位时间,即它最早
可以于 Ti 时刻开始降落,最晚可以于 Ti + Di 时刻开始降落。降落过程需要 Li个单位时间。
一架飞机降落完毕时,另一架飞机可以立即在同一时刻开始降落,但是不能在前一架飞机完成降落前开始降落。
请你判断 N 架飞机是否可以全部安全降落。
输入格式:
输入包含多组数据。
第一行包含一个整数 T,代表测试数据的组数。
对于每组数据,第一行包含一个整数 N。
以下 N 行,每行包含三个整数:Ti,Di 和 Li。
2 3 0 100 10 10 10 10 0 2 20 3 0 10 20 10 10 20 20 10 20
输出格式:
对于每组数据,输出 YES 或者 NO,代表是否可以全部安全降落。
YES NO
提示:
对于第一组数据,可以安排第 3 架飞机于 0 时刻开始降落,20 时刻完成降落。安排第 2 架飞机于 20 时刻开始降落,30 时刻完成降落。安排第 1 架飞机于 30 时刻开始降落,40 时刻完成降落。
对于第二组数据,无论如何安排,都会有飞机不能及时降落。
对于 30% 的数据,N ≤ 2。
对于 100% 的数据,1 ≤ T ≤ 10,1 ≤ N ≤ 10,0 ≤ Ti , Di , Li ≤ 105。
思路:
我们只需模拟下去就行,首先你会看到dfs中我有n个分支,这里就是取最佳能降落的飞机过程,然后让他降落,如果这个决策在后面发现是错误的,那就回溯,重新进行这个决策即可,不断重复直到把所有飞机降落完成就行了。
细节:
首先就是我写的这个bool dfs函数有个特点,就是只要找到答案就会立刻结束整个dfs过程,那么我们当然希望它能够今早的找到答案了,这样能加节约时间,所以我对plane进行了排序,以便每次都能尽量选取正确的决策。
然后就行说一下我当时做这个题遇到的一个坑:那就是下个过来的飞机可能会很晚,因此这种情况下应该上个飞机降落后时间应该再推迟一下,才能赶上下一个新来的飞机,所以我用了max函数。
*/
#include <iostream> //飞机降落:每架飞机到达时间t,盘旋时间d,降落时间l,
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;
int T,n;
int vis[11];
struct plane{
int t,d,l;
int lasttime;
}p[11];
bool cmp(plane p1,plane p2){ //只要dfs越早找到答案就越早返回true结束dfs,所有排序很有必要
return p1.lasttime<p2.lasttime;
}
bool dfs(int cnt,int time){
if(cnt==n) return true;
for(int i=0;i<n;i++){
if(vis[i]) continue; //判断过程:time是上架飞机降落后的时间,下一架飞机只要能比这个时间能更晚开始降落,那就尝试降落
vis[i]=1; //降落后的时间确定:可能下个飞机需要盘旋才行,也有可能过了好久才到达,这里是个坑
if(time<=p[i].t+p[i].d&&dfs(cnt+1,max(time,p[i].t)+p[i].l))return true;//先让当前飞机能降落,再让后面的飞机都能降落,这样能快速的结束dfs过程
vis[i]=0;
}
return false;
}
int main(){
int T;int cnt=0;
cin>>T; string ans[T];
while(T--){
cin>>n;
for(int i=0;i<n;i++){
cin>>p[i].t>>p[i].d>>p[i].l;
p[i].lasttime=p[i].t+p[i].l;
}
sort(p,p+n,cmp);
if(dfs(0,0)) ans[cnt]="YES";
else ans[cnt]="NO";
cnt++; //保存答案一块输出
memset(p,0,sizeof(p));
memset(vis,0,sizeof(vis));
}
for(int i=0;i<cnt;i++){
cout<<ans[i]<<'\n';
}
return 0;
}
岛屿个数
小蓝得到了一副大小为 M × N 的格子地图,可以将其视作一个只包含字符‘0’(代表海水)和 ‘1’(代表陆地)的二维数组,地图之外可以视作全部是海水,每个岛屿由在上/下/左/右四个方向上相邻的 ‘1’ 相连接而形成。
在岛屿 A 所占据的格子中,如果可以从中选出 k 个不同的格子,使得他们的坐标能够组成一个这样的排列:(x0, y0),(x1, y1), . . . ,(xk−1, yk−1),其中(x(i+1)%k , y(i+1)%k) 是由 (xi , yi) 通过上/下/左/右移动一次得来的 (0 ≤ i ≤ k − 1),
此时这 k 个格子就构成了一个 “环”。如果另一个岛屿 B 所占据的格子全部位于这个 “环” 内部,此时我们将岛屿 B 视作是岛屿 A 的子岛屿。若 B 是 A 的子岛屿,C 又是 B 的子岛屿,那 C 也是 A 的子岛屿。
请问这个地图上共有多少个岛屿?在进行统计时不需要统计子岛屿的数目。
输入格式:
第一行一个整数 T,表示有 T 组测试数据。
接下来输入 T 组数据。对于每组数据,第一行包含两个用空格分隔的整数M、N 表示地图大小;接下来输入 M 行,每行包含 N 个字符,字符只可能是‘0’ 或 ‘1’。
2 5 5 01111 11001 10101 10001 11111 5 6 111111 100001 010101 100001 111111
输出格式:
1 3
提示:
对于第一组数据,包含两个岛屿,下面用不同的数字进行了区分:
01111
11001
10201
10001
11111
岛屿 2 在岛屿 1 的 “环” 内部,所以岛屿 2 是岛屿 1 的子岛屿,答案为 1。
对于第二组数据,包含三个岛屿,下面用不同的数字进行了区分:
111111
100001
020301
100001
111111
注意岛屿 3 并不是岛屿 1 或者岛屿 2 的子岛屿,因为岛屿 1 和岛屿 2 中均没有“环”。
对于 30% 的评测用例,1 ≤ M, N ≤ 10。
对于 100% 的评测用例,1 ≤ T ≤ 10,1 ≤ M, N ≤ 50。
思路:
首先,我们对这个图处理一下,有水的地方标记成0,陆地标记成1。
第一步,我们先跑一下海水的地方,把有水的地方染成成2,这样的话就很好判断环岛和孤岛了,如果是环岛的话,那么里面肯定有为0的水,而孤岛的话就……就是一个普通的破岛,没有为0的水的。
第二步,我们再跑一下陆地,如果遇到0(最开始的海水),那么就说明次岛是环岛,否则就是孤岛。然后数岛的话呢,直接把0和1都给擦除,然后ans++即可
#include<bits/stdc++.h>
using namespace std;
const int N = 5e1 + 5;
int T, m, n,ans;
char g[N][N];
int dx[8] = {0, 0, 1, -1, 1, 1, -1, -1};
int dy[8] = {1, -1, 0, 0, -1, 1, 1, -1};
struct point {
int x, y;
};
void bfs1() { //从外面的海水开始,把水染成2,剩下的1都代表陆地,只有环岛内才有0(水)
queue<point>q;
g[0][0] = '2';
q.push({0, 0});
while (!q.empty()) {
point now = q.front(); q.pop();
for (int i=0; i<8; i++) {
int tx=now.x+dx[i]; int ty=now.y+dy[i];
if (tx>=0 && ty>=0 && tx<=m+1 && ty<=n+1) {//注意边界
if (g[tx][ty]=='0') {
g[tx][ty]='2'; q.push({tx, ty});
}
}
}
}
}
void bfs2(int x, int y) { //遍历过的陆地也染成2,陆地还能遇见原来的水(有0)就说明是环岛,遇不到就是普通岛
queue<point>q;
g[x][y]='2';
q.push({x, y});
while (!q.empty()) {
point now = q.front();
q.pop();
for (int i=0; i<4; i++) {
int tx=now.x+dx[i]; int ty=now.y+dy[i];
if (tx>=1 && ty>=1 && tx<=m && ty<=n) { //注意边界
if (g[tx][ty]=='0' || g[tx][ty]=='1') {//如果是环的话,就1和0都算; 如果不是环,那就没0的事
g[tx][ty]='2'; q.push({tx, ty});
}
}
}
}
}
int main() {
cin>>T;
while (T--) {
cin>>m>>n;
memset(g,'0',sizeof(g)); ans=0;
for (int i=1; i<=m; i++) {//一定,一定要在外侧填上海水
for (int j=1; j<=n; j++) {
cin>>g[i][j];
}
}
bfs1();//先在外面把海水都染成2,这样剩下的都是环(因为外面的水进不去环)
for (int i=1; i<=m; i++) {
for (int j=1; j<=n; j++) {
if (g[i][j]=='1') {
ans++; bfs2(i, j);//开始搜索岛屿
}
}
}
cout<<ans<<endl;
}
return 0;
}
接龙数列
对于一个长度为 K 的整数数列:A1, A2, . . . , AK,我们称之为接龙数列当且仅当 Ai 的首位数字恰好等于 Ai−1 的末位数字 (2 ≤ i ≤ K)。
例如 12, 23, 35, 56, 61, 11 是接龙数列;12, 23, 34, 56 不是接龙数列,因为 56的首位数字不等于 34 的末位数字。所有长度为 1 的整数数列都是接龙数列。
现在给定一个长度为 N 的数列 A1, A2, . . . , AN,请你计算最少从中删除多少个数,可以使剩下的序列是接龙序列?
输入格式:
第一行包含一个整数 N。
第二行包含 N 个整数 A1, A2, . . . , AN。
5 11 121 22 12 2023
输出格式:
1
提示:
删除 22,剩余 11, 121, 12, 2023 是接龙数列。
对于 20% 的数据,1 ≤ N ≤ 20。
对于 50% 的数据,1 ≤ N ≤ 10000。
对于 100% 的数据,1 ≤ N ≤ 105,1 ≤ Ai ≤ 109。所有 Ai 保证不包含前导 0。
思路:
完全就是一道动态规划的题,类型和最长上升子序列一模一样(不明白的可以去搜一下那个题),状态转移方程都是由前面所有状态得出:dp[i]=max(dp[j]+1)(j<i)
个人感觉:动态规划无非两种转移方程:第一种是由前几个状态得来;第二种是由前面所有状态得来。
#include <iostream> //最长接龙数列 求最少要删除的个数
using namespace std;
int dp[100000],a[100000]; //dp[i]表示以第i个数结尾的最长接龙数列需要删数的个数,要求的就是dp[n]
bool check(int j,int i){
int head,end=j%10;
while(i){
head=i%10;i/=10;
}
if(head==end) return true;
return false;
}
int main(){
int n,ans=-1;cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
dp[0]=1;
for(int i=0;i<n;i++){
for(int j=0;j<i;j++){
if(check(a[j],a[i])) {
dp[i]=max(dp[i],dp[j]+1);} //状态转移方程
}
}
for(int i=0;i<n;i++){
ans=max(dp[i],ans);
}
cout<<n-ans;//输出要删除的个数
return 0;
}
子串简写
程序猿圈子里正在流行一种很新的简写方法:对于一个字符串,只保留首尾字符,将首尾字符之间的所有字符用这部分的长度代替。例如 internation-alization 简写成 i18n,Kubernetes (注意连字符不是字符串的一部分)简写成 K8s, Lanqiao 简写成 L5o 等。
在本题中,我们规定长度大于等于 K 的字符串都可以采用这种简写方法(长度小于 K 的字符串不配使用这种简写)。
给定一个字符串 S 和两个字符 c1 和 c2,请你计算 S 有多少个以 c1 开头c2 结尾的子串可以采用这种简写?
输入格式:
第一行包含一个整数 K。
第二行包含一个字符串 S 和两个字符 c1 和 c2。
4 abababdb a b
输出格式:
6
提示:
符合条件的子串如下所示,中括号内是该子串:
[abab]abdb
[ababab]db
[abababdb]
ab[abab]db
ab[ababdb]
abab[abdb]
对于 20% 的数据,2 ≤ K ≤ |S | ≤ 10000。
对于 100% 的数据,2 ≤ K ≤ |S | ≤ 5 × 105。S 只包含小写字母。c1 和 c2 都是小写字母。
|S | 代表字符串 S 的长度。
思路:
这个题的数据量很大,不能暴力,先判断再循环的双for也不行。我们不能找一个c1就遍历整个字符串一次,我们要做的应该是每次都能快速找到c1和c2,所以需要设置两个跟踪数组来标记c1和c2出现的位置,然后直接对双for循环……没了
#include <iostream> //子串简写
#include <cstring>
#include <string>
using namespace std;
int k,ans;
char ch1,ch2;
int c1[500000],c2[500000];//跟踪数组
string s;
int main(){
cin>>k;cin>>s;
int tem1=0,tem2=0,len=s.size();cin>>ch1>>ch2;
for(int i=0;i<len;i++){
if(s[i]==ch1) c1[tem1++]=i;
if(s[i]==ch2) c2[tem2++]=i;
}
for(int i=0;i<tem1;i++){
for(int j=0;j<tem2;j++){
if(c2[j]-c1[i]>=k-1)ans++;
}
}
cout<<ans;
}
日期统计
问题描述
小蓝现在有一个长度为 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,20231223。yyyy 表示年份,mm 表示月份,dd 表示天数,当月份或者天数的长度只有一位时需要一个前导零补充。
请你帮小蓝计算下按上述条件一共能找到多少个不同 的 2023 年的日期。对于相同的日期你只需要统计一次即可。
思路:一点也不难,主要是恶心,有点麻烦,要一直CTRL+C CTRL+V。这个题不能直接暴力递归,因为太慢了根本出不来答案,可以8个for循环套一起,中间if连接是可以的,因为这是先判断,再取数,这样的话速度就快起来了
#include <iostream> //日期统计
using namespace std;
int a[100],date[9],ans;
int vis[20250000];
int mm[13]={31,28,31,30,31,30,31,31,30,31,30,31};
int main(){
for(int i=0;i<100;i++){
cin>>a[i];
}
int mon,day;
for(int i=0;i<100-7;i++){
if(a[i]!=2) continue;
for(int j=i+1;j<100-6;j++){
if(a[j]!=0) continue;
for(int k=j+1;k<100-5;k++){
if(a[k]!=2) continue;
for(int l=k+1;l<100-4;l++){
if(a[l]!=3) continue;
for(int m=l+1;m<100-3;m++){
if(a[m]>1) continue;
for(int o=m+1;o<100-2;o++){
mon=a[m]*10+a[o];
if(mon>12||mon==0)continue;
for(int p=o+1;p<100-1;p++){
if(a[p]>3)continue;
for(int q=p+1;q<100;q++){
day=a[p]*10+a[q];
if(day>0&&day<=mm[mon-1]) {
day=day+mon*100+2023*10000;
if(!vis[day])ans++;vis[day]=1;
}}}}}}}}}
cout<<ans;
}
整数删除
给定一个长度为 N 的整数数列:A1, A2, . . . , AN。你要重复以下操作 K 次:
每次选择数列中最小的整数(如果最小值不止一个,选择最靠前的),将其删除。并把与它相邻的整数加上被删除的数值。输出 K 次操作后的序列。
输入格式:
第一行包含两个整数 N 和 K。
第二行包含 N 个整数,A1, A2, A3, . . . , AN。
5 3 1 4 2 8 7
输出格式:
输出 N − K 个整数,中间用一个空格隔开,代表 K 次操作后的序列。
17 7
提示:
数列变化如下,中括号里的数是当次操作中被选择的数:
[1] 4 2 8 7
5 [2] 8 7
[7] 10 7
17 7
对于 20% 的数据,1 ≤ K < N ≤ 10000。
对于 100% 的数据,1 ≤ K < N ≤ 5 × 105,0 ≤ Ai ≤ 108。
思路:
这个题思路不难,主要是优化要解决的问题是:找到最小数的下标,找到最小数的左右“最新邻居 ”并更新
那么,为原数组设置一个优先队列,以方便知道最小的数,然后也要方便拿出下标
那么,为数组元素设置跟踪的左右链表,以便快速知道左右的邻居下标(因为一个一个的跳过被删掉的数会很慢)要注意的是:
每次删数都会导致数组的一个元素被删掉,左右邻居更新,但队列中的邻居原值不会被删掉,只会增加新的,所以队头可能是无效数!类似“dijkstra+堆优化”中的问题
#include <bits/stdc++.h> //整数删除:每次都删除最小的数,同时加给左右的数,求操作k次后的数列
using namespace std; //引入优先队列,加入左右跟踪数组(下标跟踪)
const int N=5e5+10;
long long a[N];//因为数组元素要不断相加,会爆int
int l[N],r[N],n,k;//左右链表(存放左右邻居下标)
typedef pair<long long,int> pll;//重命名为pll(first为数值,second为下标)
priority_queue<pll,vector<pll>,greater<pll> >q;//pair类型(结构体)的优先队列模板
void del(int pos){
if(l[pos]>0){
a[l[pos]]+=a[pos];//更改原数组
r[l[pos]]=r[pos];//更改链表
q.push({a[l[pos]],l[pos]});//更新队列
}
if(r[pos]<=n){
a[r[pos]]+=a[pos];
l[r[pos]]=l[pos];
q.push({a[r[pos]],r[pos]});
}
a[pos]=-1;//值为-1代表删除
}
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
q.push({a[i],i});//将原数组的数值和下标一起保存起来(这样取出最小数后就可以直接根据下标找到数)
l[i]=i-1;//初始化左,右链表;(这个根据左右链表就能迅速找到未删掉左右邻居并更新值)
r[i]=i+1;
}
while(k--){
pll t=q.top();//取出最小数值给pair
q.pop();//删掉队列元素
if(a[t.second]!=t.first){//无效数,这个数已经被更新过(这种情况是因为数组中原数已经被更新值了但队列中依然原值新值都有)
k++;continue;//跳过,并恢复现场
}
del(t.second);//删除数字(直接传入下标)
}
for(int i=1;i<=n;i++){
if(a[i]!=-1)printf("%lld ",a[i]);//将维护的数组输出
}
return 0;
}
更多推荐
所有评论(0)