蓝桥杯AcWing 题目题解 - 枚举、模拟与排序

目录

AcWing 1210. 连号区间数

AcWing 1236. 递增三元组

AcWing 1204. 错误票据 

AcWing 466. 回文日期

AcWing 1219. 移动距离

AcWing 1229. 日期问题

AcWing 1231. 航班时间 

小知识:sscanf函数用法


AcWing 1210. 连号区间数

小明这些天一直在思考这样一个奇怪而有趣的问题:

在 1∼N 的某个排列中有多少个连号区间呢?

这里所说的连号区间的定义是:

如果区间 [L,R]里的所有元素(即此排列的第 LL 个到第 RR 个元素)递增排序后能得到一个长度为 R−L+1 的“连续”数列,则称这个区间连号区间。

当 NN 很小的时候,小明可以很快地算出答案,但是当 NN 变大的时候,问题就不是那么简单了,现在小明需要你的帮助。

输入格式

第一行是一个正整数 N,表示排列的规模。

第二行是 N 个不同的数字 Pi,表示这 N 个数字的某一排列。

输出格式

输出一个整数,表示不同连号区间的数目。

数据范围

1≤N≤10000
1≤Pi≤N

输入样例1:

4
3 2 4 1

输出样例1:

7

输入样例2:

5
3 4 2 5 1

输出样例2:

9

样例解释

第一个用例中,有 7 个连号区间分别是:[1,1],[1,2],[1,3],[1,4],[2,2],[3,3],[4,4][1,1],[1,2],[1,3],[1,4],[2,2],[3,3],[4,4]
第二个用例中,有 9 个连号区间分别是:[1,1],[1,2],[1,3],[1,4],[1,5],[2,2],[3,3],[4,4],[5,5]

思路:

两重循环枚举区间左端点和右端点
判定区间为连号区间:
连号区间的性质是区间内排序后为递增序列差值为1,又1~n排列暗示没有重复数,因此可以转化为区间最大最小值作差为区间长度

因为是连续的,所以在所取的[l,r]范围中寻找最大值,最小值。然后相减,最后和r-l(区间长度)作比较即可。

#include<iostream>
using namespace std;
const int N=10010,X=1e8;
int n;
int a[N];

int main()
{
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    
    int res=0;
    for(int i=0;i<n;i++)    //左端点
    {
        int minv=X,maxv=-X;
        for(int j=i;j<n;j++)    //右端点
        {
            minv=min(minv,a[j]);
            maxv=max(maxv,a[j]);
            if(maxv-minv==j-i) res++;
        }
        
    }
    cout<<res<<endl;
    return 0;
}

AcWing 1236. 递增三元组

输入样例:

3
1 1 1
2 2 2
3 3 3

输出样例:

27

 思路:二分 先对三个数组进行sort排序,然后遍历b数组,对于b中的每一个数b[i],在a数组中寻找最后一个
小于b[i]的数的下标,这里我们记做l.在再c数组中寻找第一个大于b[i]的数的下标,这里我们记做r
可知a数组中,小于b[i]的数的个数为l+1,c数组中大于b[i]数的个数为n-r.(下标从0开始)
因此在三元递增组中,以b[i]为中间数的个数为(l+1)*(n-r).
遍历b数组,res+=(l+1)*(n-r) 即为答案

注:res += (LL)(x + 1)*(n - y);一定要强制转换为long long类型

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N], b[N], c[N];
int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++)  scanf("%d", &a[i]);
    for (int i = 0; i < n; i++)  scanf("%d", &b[i]);
    for (int i = 0; i < n; i++)  scanf("%d", &c[i]);
    sort(a, a + n);  //二分需要满足单调性
    sort(b, b + n);
    sort(c, c + n);
    LL res = 0;  //答案可能会很大,会爆int
    for (int i = 0; i < n; i++)
    {
        int l = 0, r = n - 1;  //二分查找a数组中最后一个小于b[i]的数的下标
        while (l < r)
        {
            int mid = (l + r + 1) / 2;
            if (a[mid] < b[i])   l = mid;
            else   r = mid - 1;
        }
        if (a[l] >= b[i])   //如果未找到小于b[i]的数,将x标记为-1,后续计算时 x+1==0
        {
            l = -1;
        }
        int x = l;
        l = 0, r = n - 1;
        while (l < r)
        {
            int mid = (l + r) / 2;
            if (c[mid] > b[i])   r = mid;
            else  l = mid + 1;
        }
        if (c[l] <= b[i])   //如果未找到大于b[i]的数,将y标记为n,后续计算时 n-y==0;
        {
            r = n;
        }
        int y = r;
        res += (LL)(x + 1)*(n - y);    //防爆;
    }
    printf("%lld\n", res);
    return 0;
}

STL写法

lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
 

样例中(lower_bound(a + 1, a + 1 + n, b[i]) - a) 返回了 4 ;

(upper_bound(c + 1, c + 1 + n, b[i]) - c)返回了 1 ;

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long lld;
const int N = 100005;
int a[N], b[N], c[N];
int n;
lld sum;

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++)
        scanf("%d", &b[i]);
    for (int i = 1; i <= n; i++)
        scanf("%d", &c[i]);

    //由于二分的前提是单调序列 所以预先对a b c排序 直接sort
    sort(a + 1, a + 1 + n);
    sort(b + 1, b + 1 + n);
    sort(c + 1, c + 1 + n);

    for (int i = 1; i <= n; i++)
    {
        //直接用STL中的两个二分函数解决
        lld x = (lower_bound(a + 1, a + 1 + n, b[i]) - a) - 1; //在数组a中找比b[i]小的数
        lld y = n - (upper_bound(c + 1, c + 1 + n, b[i]) - c) + 1;//在数组c中找比b[i]大的数
        sum += x * y;
    }

    printf("%lld", sum);
    return 0;
}

 AcWing 1204. 错误票据 

某涉密单位下发了某种票据,并要在年终全部收回。

每张票据有唯一的ID号。

全年所有票据的ID号是连续的,但ID的开始数码是随机选定的。

因为工作人员疏忽,在录入ID号的时候发生了一处错误,造成了某个ID断号,另外一个ID重号。

你的任务是通过编程,找出断号的ID和重号的ID。

假设断号不可能发生在最大和最小号。

输入格式

第一行包含整数 N,表示后面共有 N 行数据。

接下来 N 行,每行包含空格分开的若干个(不大于100个)正整数(不大于100000),每个整数代表一个ID号。

输出格式

要求程序输出1行,含两个整数 m,n用空格分隔。

其中,m表示断号ID,n表示重号ID。

数据范围

1≤N≤100

输入样例:

2
5 6 8 11 9 
10 12 9

输出样例:

7 9
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int M=100010;

int cnt[M];

int main(){
    int line,Min=100001,Max=0,a;
    int n,m; //m表示断号ID,n表示重号ID
    cin>>line;
    while(line--){
        while(cin>>a){
            cnt[a]++;
            Max=max(Max,a);
            Min=min(Min,a);
        }
    }

    for(int j=Min;j<Max;j++){
        if(cnt[j]==0) m=j;
        else if(cnt[j]==2) n=j;
    }

    cout<<m<<" "<<n<<endl;
    return 0;
}

 AcWing 466. 回文日期

输入样例:

20110101
20111231

输出样例:

1
#include<iostream>
using namespace std;

int m[13] = {0 ,31 ,28 ,31 ,30 ,31 ,30 ,31 ,31 ,30 ,31 ,30 ,31 }; 

bool check(int date)
{
    int year = date / 10000;
    int mon = (date % 10000) / 100;
    int day = date % 100;

    //求闰年 : 四年一润,百年不润,千年润
    if((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) m[2] = 29;
    else m[2] = 28;

    //判断日,月是否符合
    if( day > m[mon]) return false;
    if( mon > 12) return false;
    return true;

}

int main()
{
    int date1 , date2;
    cin>>date1>>date2;

    int ans = 0;//答案

    //枚举1000 ~ 9999的数字 , 因为这里是回文数,只要要枚举出来一半即可
    for(int i = 1000 ; i<=9999 ; i++)
    {
        //将另一半算出来
        int ri = i;
        for(int j = i ; j ; j/=10) ri = ri*10 + j%10;//短除法求出位数,然后加上去

        if(date1 <= ri && ri <= date2 && check(ri)) ans ++;
    }

    cout<<ans;

    return 0;
}

AcWing 1219. 移动距离

X星球居民小区的楼房全是一样的,并且按矩阵样式排列。

其楼房的编号为 1,2,3…
当排满一行时,从下一行相邻的楼往反方向排号。

比如:当小区排号宽度为 6 时,开始情形如下:

1 2 3 4 5 6
12 11 10 9 8 7
13 14 15 .....
我们的问题是:已知了两个楼号 m 和 n,需要求出它们之间的最短移动距离(不能斜线方向移动)。

输入格式
输入共一行,包含三个整数 w,m,n,w 为排号宽度,m,n 为待计算的楼号。

输出格式
输出一个整数,表示 m,n 两楼间最短移动距离。

数据范围
1≤w,m,n≤10000,

输入样例:

6 8 2

输出样例:

4

 思路:

 将每行顺序写出,并将序号减1,便于利用公式求行号和列号;从0行开始,奇数行实际列号为:

(w-1) - y;//因为序号减1,反转时也要 -1

#include <bits/stdc++.h>

using namespace std;

int main(){
    int w, m, n;
    cin >> w >> m >> n;
    m --, n --;//序号减1,便于利用公式求行号和列号

    int x1 = m / w, x2 = n / w;//行号
    int y1 = m % w, y2 = n % w;//求列号
    if(x1 & 1) y1 = w - 1 - y1;//特判,是否为奇数行
    if(x2 & 1) y2 = w - 1 - y2;

    //曼哈顿顿距离
    cout << abs(x1 - x2) +abs(y1 - y2) << endl;
    return 0;
}

小知识:x & 1等价于(x & 1)==1等价于x %2==1;(判断奇数)

//(x & 1)==0等价于x %2==0;

AcWing 1229. 日期问题

小明正在整理一批历史文献。这些历史文献中出现了很多日期。

小明知道这些日期都在1960年1月1日至2059年12月31日。

令小明头疼的是,这些日期采用的格式非常不统一,有采用年/月/日的,有采用月/日/年的,还有采用日/月/年的。

更加麻烦的是,年份也都省略了前两位,使得文献上的一个日期,存在很多可能的日期与其对应。

比如02/03/04,可能是2002年03月04日、2004年02月03日或2004年03月02日。

给出一个文献上的日期,你能帮助小明判断有哪些可能的日期对其对应吗?

输入格式
一个日期,格式是”AA/BB/CC”。

即每个’/’隔开的部分由两个 0-9 之间的数字(不一定相同)组成。

输出格式
输出若干个不相同的日期,每个日期一行,格式是”yyyy-MM-dd”。

多个日期按从早到晚排列。

数据范围
0≤A,B,C≤9

输入样例:

02/03/04

输出样例:

2002-03-04
2004-02-03
2004-03-02

 解题思路:纯暴力,搞他就完事了;

#include<iostream>
#include<cstdio>
using namespace std;
int days[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};

int check(int year,int mon,int day)
{
    if(year%4==0&&year%100!=0||year%400==0) days[2]=29;//判断闰年
    else days[2]=28;
    if(mon==0||mon>12) return 0;
    if(day==0||day>days[mon]) return 0;
}
int main()
{
    int a,b,c;
    scanf("%d/%d/%d",&a,&b,&c);
    
    for(int i=19600101;i<=20591231;i++)
    {
        int year=i/10000,mon=i%10000/100,day=i%100;
        if(check(year,mon,day))
        {
            if(year%100==a&&mon==b&&day==c||
            year%100==c&&mon==a&&day==b||
            year%100==c&&mon==b&&day==a)
            
            printf("%d-%02d-%02d\n",year,mon,day);//补前导0
        }
    }
    return 0;
}

AcWing 1231. 航班时间 

小 h 前往美国参加了蓝桥杯国际赛。

小 h 的女朋友发现小 h 上午十点出发,上午十二点到达美国,于是感叹到“现在飞机飞得真快,两小时就能到美国了”。

小 h 对超音速飞行感到十分恐惧。

仔细观察后发现飞机的起降时间都是当地时间。

由于北京和美国东部有 12 小时时差,故飞机总共需要 14 小时的飞行时间。

不久后小 h 的女朋友去中东交换。

小 h 并不知道中东与北京的时差。

但是小 h 得到了女朋友来回航班的起降时间。

小 h 想知道女朋友的航班飞行时间是多少。

对于一个可能跨时区的航班,给定来回程的起降时间。

假设飞机来回飞行时间相同,求飞机的飞行时间。

输入格式
一个输入包含多组数据。

输入第一行为一个正整数 T,表示输入数据组数。

每组数据包含两行,第一行为去程的起降时间,第二行为回程的起降时间。

起降时间的格式如下:

h1:m1:s1 h2:m2:s2
h1:m1:s1 h3:m3:s3 (+1)
h1:m1:s1 h4:m4:s4 (+2)
第一种格式表示该航班在当地时间h1时m1分s1秒起飞,在当地时间当日h2时m2分s2秒降落。

第二种格式表示该航班在当地时间h1时m1分s1秒起飞,在当地时间次日h2时m2分s2秒降落。

第三种格式表示该航班在当地时间h1时m1分s1秒起飞,在当地时间第三日h2时m2分s2秒降落。

输出格式
对于每一组数据输出一行一个时间hh:mm:ss,表示飞行时间为hh小时mm分ss秒。

注意,当时间为一位数时,要补齐前导零,如三小时四分五秒应写为03:04:05。

数据范围
保证输入时间合法(0≤h≤23,0≤m,s≤59),飞行时间不超过24小时。

输入样例:

3
17:48:19 21:57:24
11:05:18 15:14:23
17:21:07 00:31:46 (+1)
23:02:41 16:13:20 (+1)
10:19:19 20:41:24
22:19:04 16:41:09 (+1)

输出样例:

04:09:05
12:10:39
14:22:05

题解思路
1.去程的降落时间 = 飞行时间 + 时差 + 去程的起飞时间
=>飞行时间 = 去程的降落时间 - 时差 - 去程的起飞时间 ①

2.回程的降落时间 = 飞行时间 - 时差 + 回程的起飞时间
=>飞行时间 = 回程的降落时间 + 时差 - 回程的起飞时间 ②

[① + ②] / 2 => 飞行时间 = [(去程的降落时间 - 去程的起飞时间) + (回程的降落时间 - 回程的起飞时间)] / 2 

小知识:

sscanf函数用法:

1、int sscanf(const char *str, const char *format, ...) 从字符串读取格式化输入

#include <stdio.h>
int main(void)
{
    char str[100] ="123568qwerSDDAE";
    char lowercase[100],sztime1[100], sztime2[100];
    int num,a,b,c;
    sscanf(str,"%d %[a-z]", &num, lowercase);
    sscanf("2006:03:18", "%d:%d:%d", &a, &b, &c);
    sscanf("2006:03:18 - 2006:04:18", "%s - %s", sztime1, sztime2);
    
    
    
    printf("The number is: %d.\n", num);
    printf("The lowercase is: %s.\n", lowercase);
    printf("%d年 %d月 %d日\n",a,b,c);
    printf("%s 和 %s\n",sztime1, sztime2);
    return 0;
}

(1)sscanf("zhoue3456 ", "%4s", str); //取指定长度的字符串       
          printf("str=%s\n", str);   //str="zhou";


(2)sscanf("zhou456 hedf", "%[^ ]", str); //取到指定字符为止的字符串,取遇到空格为止字符串    
          printf("str=%s\n", str);  //str=zhou456;


(3)sscanf("654321abcdedfABCDEF", "%[1-9a-z]", str); //取仅包含指定字符集的字符串
          printf("str=%s\n", str);  //str=654321abcded,只取数字和小写字符


(4)sscanf("BCDEF123456abcdedf", "%[^a-z]", str); //取到指定字符集为止的字符串       
           printf("str=%s\n", str);  //  str=BCDEF123456, 取遇到小写字母为止的字符串 

(5)int a,b,c;
          sscanf("2015.04.05", "%d.%d.%d", &a,&b,&c); //取需要的字符串   
          printf("a=%d,b=%d,c=%d",a,b,c);  //  a=2015,b=4,c=5
 

#include <bits/stdc++.h>
using namespace std;

int get_seconds(int h, int m, int s)  //转换为秒
{
    return h * 3600 + m * 60 + s;    
}

int get_time()   //得到单次起降的时间差
{
    string line;
    getline(cin, line);
    if(line.back() != ')')  line += " (+0)";

    int h1, m1, s1, h2, m2, s2, d;
    sscanf(line.c_str(), "%d:%d:%d %d:%d:%d (+%d)", &h1, &m1, &s1, &h2, &m2, &s2, &d);

    return get_seconds(h2, m2, s2) - get_seconds(h1, m1, s1) + d * 24 * 3600;
}

int main()
{
    int T;
    cin >> T;

    getchar();  //因为要用getline读取行,所以要用getchar()吃掉第一行的回车
    while (T -- )
    {
        int time = get_time() + get_time() >> 1;    //计算飞行时间, >> 1 相当于 / 2 且 “>>” 优先级低于“+”

        int hour = time / 3600;    //要牢记三个公式,用于时间转换!
        int minute = time % 3600 / 60;  
        int second = time % 60;

        printf("%02d:%02d:%02d\n", hour, minute, second);
    }
    return 0;
}

  • 15
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NO.-LL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值