【蓝桥杯算法练习题】枚举、模拟与排序

10 篇文章 4 订阅
10 篇文章 0 订阅

一、AcWing 1210. 连号区间数

【题目描述】
小明这些天一直在思考这样一个奇怪而有趣的问题:
1 ∼ N 1\sim N 1N的某个排列中有多少个连号区间呢?
这里所说的连号区间的定义是:
如果区间 [ L , R ] [L,R] [L,R]里的所有元素(即此排列的第 L L L个到第 R R R个元素)递增排序后能得到一个长度为 R − L + 1 R-L+1 RL+1的“连续”数列,则称这个区间连号区间。
N N N很小的时候,小明可以很快地算出答案,但是当 N N N变大的时候,问题就不是那么简单了,现在小明需要你的帮助。

【输入格式】
第一行是一个正整数 N N N,表示排列的规模。
第二行是 N N N个不同的数字 P i P_i Pi,表示这 N N N个数字的某一排列。

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

【数据范围】
1 ≤ N ≤ 10000 1≤N≤10000 1N10000
1 ≤ P i ≤ N 1≤P_i≤N 1PiN

【输入样例1】

4
3 2 4 1

【输出样例1】

7

【输入样例2】

5
3 4 2 5 1

【输出样例2】

9

【样例解释】
第一个用例中,有 7 7 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] [1,1],[1,2],[1,3],[1,4],[2,2],[3,3],[4,4]
第二个用例中,有 9 9 9个连号区间分别是: [ 1 , 1 ] , [ 1 , 2 ] , [ 1 , 3 ] , [ 1 , 4 ] , [ 1 , 5 ] , [ 2 , 2 ] , [ 3 , 3 ] , [ 4 , 4 ] , [ 5 , 5 ] [1,1],[1,2],[1,3],[1,4],[1,5],[2,2],[3,3],[4,4],[5,5] [1,1],[1,2],[1,3],[1,4],[1,5],[2,2],[3,3],[4,4],[5,5]

【分析】


由于给定的序列是 1 ∼ N 1\sim N 1N的某种排列,即 1 ∼ N 1\sim N 1N中的每个数有且仅出现一次。因此如果某个区间 [ L , R ] [L,R] [L,R]是连续区间,那么区间中的最大值 m a x v maxv maxv减去区间中的最小值 m i n v minv minv一定等于 R − L R-L RL

根据该性质,我们可以枚举区间的左端点 i i i,对于每个 i i i我们再枚举区间的右端点 j j j,在从小到大枚举 j j j的时候我们可以看成每次区间中新加入一个数 a [ j ] a[j] a[j],因此可以动态的维护区间 [ i , j ] [i,j] [i,j]中的最大值与最小值。该做法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),且常数很小,可以通过本题。


【代码】

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

const int N = 10010;
int a[N];
int n, res;

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++) cin >> a[i];
    for (int i = 0; i < n; i++)
    {
        int minv = 1e9, maxv = -1e9;
        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. 递增三元组

【题目描述】
给定三个整数数组:

A = [ A 1 , A 2 , … , A N ] A=[A_1,A_2,\dots ,A_N] A=[A1,A2,,AN]
B = [ B 1 , B 2 , … , B N ] B=[B_1,B_2,\dots ,B_N] B=[B1,B2,,BN]
C = [ C 1 , C 2 , … , C N ] C=[C_1,C_2,\dots ,C_N] C=[C1,C2,,CN]

请你统计有多少个三元组 ( i , j , k ) (i,j,k) (i,j,k)满足:

  • 1 ≤ i , j , k ≤ N 1≤i,j,k≤N 1i,j,kN
  • A i < B j < C k A_i<B_j<C_k Ai<Bj<Ck

【输入格式】
第一行包含一个整数 N N N
第二行包含 N N N个整数 A 1 , A 2 , … , A N A_1,A_2,\dots ,A_N A1,A2,,AN
第三行包含 N N N个整数 B 1 , B 2 , … , B N B_1,B_2,\dots ,B_N B1,B2,,BN
第四行包含 N N N个整数 C 1 , C 2 , … , C N C_1,C_2,\dots ,C_N C1,C2,,CN

【输出格式】
一个整数表示答案。

【数据范围】
1 ≤ N ≤ 1 0 5 1≤N≤10^5 1N105
0 ≤ A i , B i , C i ≤ 1 0 5 0≤A_i,B_i,C_i≤10^5 0Ai,Bi,Ci105

【输入样例】

3
1 1 1
2 2 2
3 3 3

【输出样例】

27

【分析】


最暴力的做法就是三重循环枚举 i , j , k i,j,k i,j,k,时间复杂度为 O ( n 3 ) O(n^3) O(n3),只能过部分样例。

本题的数据量允许我们只能枚举一个序列,如果枚举 A A A,则 B , C B,C B,C中选出的数的相对大小关系不好确定,枚举 C C C同理。而如果我们枚举 B B B,那么我们只需要求出 A A A中小于 B i B_i Bi的数的数量 c n t 1 cnt1 cnt1,以及 C C C中大于 B i B_i Bi的数的数量 c n t 2 cnt2 cnt2,那么将 c n t 1 ∗ c n t 2 cnt1*cnt2 cnt1cnt2累加到结果中即可。

求出 c n t 1 , c n t 2 cnt1,cnt2 cnt1,cnt2的方式有很多,可以排序+二分或者排序+双指针。本题以双指针为例,首先将三个数组排好序,设置指针 i d x 1 = i d x 2 = 0 idx1=idx2=0 idx1=idx2=0分别指向 A A A C C C(三个数组下标均从 0 0 0开始),然后我们从小到大枚举 B i B_i Bi。对于每个 B i B_i Bi,我们先更新 i d x 1 , i d x 2 idx1,idx2 idx1,idx2的位置, i d x 1 idx1 idx1指向的是数组 A A A中第一个大于等于 B i B_i Bi的位置, i d x 2 idx2 idx2指向的是数组 C C C中第一个大于 B i B_i Bi的位置,由于 B i B_i Bi是单调递增的,因此两个指针也一定是单调递增的。则 i d x 1 idx1 idx1即为 c n t 1 cnt1 cnt1 n − i d x 2 n-idx2 nidx2即为 c n t 2 cnt2 cnt2,因此答案累加上 i d x 1 ∗ ( n − i d x 2 ) idx1*(n-idx2) idx1(nidx2)即可。


【代码】

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

typedef long long LL;
const int N = 100010;
int a[N], b[N], c[N];
int n;

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++) cin >> a[i];
    for (int i = 0; i < n; i++) cin >> b[i];
    for (int i = 0; i < n; i++) cin >> c[i];
    sort(a, a + n); sort(b, b + n); sort(c, c + n);
    int idx1 = 0, idx2 = 0;
    LL res = 0;
    for (int i = 0; i < n; i++)
    {
        while (idx1 < n && a[idx1] < b[i]) idx1++;
        while (idx2 < n && c[idx2] <= b[i]) idx2++;
        res += (LL)idx1 * (n - idx2);
    }
    cout << res << endl;
    return 0;
}

三、AcWing 1245. 特别数的和

【题目描述】
小明对数位中含有 2 , 0 , 1 , 9 2,0,1,9 2,0,1,9的数字很感兴趣(不包括前导 0 0 0),在 1 ∼ 40 1\sim 40 140中这样的数包括 1 , 2 , 9 , 10 , … , 32 , 39 , 40 1,2,9,10,\dots ,32,39,40 1,2,9,10,,32,39,40,共 28 28 28个,他们的和是 574 574 574
请问,在 1 ∼ n 1\sim n 1n中,所有这样的数的和是多少?

【输入格式】
共一行,包含一个整数 n n n

【输出格式】
共一行,包含一个整数,表示满足条件的数的和。

【数据范围】
1 ≤ n ≤ 10000 1≤n≤10000 1n10000

【输入样例】

40

【输出样例】

574

【分析】


直接暴力即可~


【代码】

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

int n, res;

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int x = i;
        while (x)
        {
            int t = x % 10;
            x /= 10;
            if (t == 2 || t == 0 || t == 1 || t == 9) { res += i; break; }
        }
    }
    cout << res << endl;
    return 0;
}

四、AcWing 1204. 错误票据

【题目描述】
某涉密单位下发了某种票据,并要在年终全部收回。
每张票据有唯一的ID号。
全年所有票据的ID号是连续的,但ID的开始数码是随机选定的。
因为工作人员疏忽,在录入ID号的时候发生了一处错误,造成了某个ID断号,另外一个ID重号。
你的任务是通过编程,找出断号的ID和重号的ID。
假设断号不可能发生在最大和最小号。

【输入格式】
第一行包含整数 N N N,表示后面共有 N N N行数据。
接下来 N N N行,每行包含空格分开的若干个(不大于 100 100 100个)正整数(不大于 100000 100000 100000),每个整数代表一个ID号。

【输出格式】
要求程序输出 1 1 1行,含两个整数 m , n m,n m,n,用空格分隔。
其中, m m m表示断号ID, n n n表示重号ID。

【数据范围】
1 ≤ N ≤ 100 1≤N≤100 1N100

【输入样例】

2
5 6 8 11 9 
10 12 9

【输出样例】

7 9

【分析】


将读入的所有数据排好序,然后遍历一遍找出重号与断号即可。注意本题的读入由于每一行的长度不定,因此可以使用stringstream读入或者直接忽略行数直接读到空为止。以下是stringstream的用法:

#include <iostream>
#include <string>
#include <sstream>
using namespace std;

const int N = 100010;
int a[N];
int n, cnt;

int main()
{
	cin >> n;
	string line;
	getline(cin, line);
	while (n--)
	{
		getline(cin, line);
		stringstream ssin(line);
		while (ssin >> a[cnt]) cnt++;
	}
}

【代码】

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

const int N = 10010;
int a[N];
int n, cnt;

int main()
{
    cin >> n;
    while (cin >> a[cnt]) cnt++;
    sort(a, a + cnt);
    int res1, res2;
    for (int i = 0; i < cnt - 1; i++)
        if (a[i] == a[i + 1]) res2 = a[i];
        else if (a[i + 1] - a[i] != 1) res1 = a[i] + 1;
    cout << res1 << ' ' << res2 << endl;
    return 0;
}

五、AcWing 466. 回文日期

【题目描述】
在日常生活中,通过年、月、日这三个要素可以表示出一个唯一确定的日期。
牛牛习惯用 8 8 8位数字表示一个日期,其中,前 4 4 4位代表年份,接下来 2 2 2位代表月份,最后 2 2 2位代表日期。
显然:一个日期只有一种表示方法,而两个不同的日期的表示方法不会相同。
牛牛认为,一个日期是回文的,当且仅当表示这个日期的 8 8 8位数字是回文的。
现在,牛牛想知道:在他指定的两个日期之间(包含这两个日期本身),有多少个真实存在的日期是回文的。
一个 8 8 8位数字是回文的,当且仅当对于所有的 i ( 1 ≤ i ≤ 8 ) i(1≤i≤8) i(1i8)从左向右数的第 i i i个数字和第 9 − i 9-i 9i个数字(即从右向左数的第 i i i个数字)是相同的。

例如:

  • 对于 2016 2016 2016 11 11 11 19 19 19日,用 8 8 8位数字 20161119 20161119 20161119表示,它不是回文的。
  • 对于 2010 2010 2010 1 1 1 2 2 2日,用 8 8 8位数字 20100102 20100102 20100102表示,它是回文的。
  • 对于 2010 2010 2010 10 10 10 2 2 2日,用 8 8 8位数字 20101002 20101002 20101002表示,它不是回文的。

每一年中都有 12 12 12个月份:
其中, 1 , 3 , 5 , 7 , 8 , 10 , 12 1,3,5,7,8,10,12 1,3,5,7,8,10,12月每个月有 31 31 31天; 4 , 6 , 9 , 11 4,6,9,11 4,6,9,11月每个月有 30 30 30天;而对于 2 2 2月,闰年时有 29 29 29天,平年时有 28 28 28天。
一个年份是闰年当且仅当它满足下列两种情况其中的一种:

  1. 这个年份是 4 4 4的整数倍,但不是 100 100 100的整数倍;
  2. 这个年份是 400 400 400的整数倍。

【输入格式】
输入包括两行,每行包括一个 8 8 8位数字。
第一行表示牛牛指定的起始日期 d a t e 1 date1 date1,第二行表示牛牛指定的终止日期 d a t e 2 date2 date2。保证 d a t e 1 date1 date1 d a t e 2 date2 date2都是真实存在的日期,且年份部分一定为 4 4 4位数字,且首位数字不为 0 0 0
保证 d a t e 1 date1 date1一定不晚于 d a t e 2 date2 date2

【输出格式】
输出共一行,包含一个整数,表示在 d a t e 1 date1 date1 d a t e 2 date2 date2之间,有多少个日期是回文的。

【输入样例】

20110101
20111231

【输出样例】

1

【分析】


第一种思路是枚举每个日期,判断是不是回文,另一种思路是枚举回文,也就是当前四位或后四位确定时整个回文串就确定了,再判断这个回文串构成的日期合不合法。


【枚举日期代码】

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

int date1, date2;
int y, m, d, gy, gm, gd;
int res;

bool check()
{
    int dy[4] = { 0 }, dm[2] = { 0 }, dd[2] = { 0 };
    int ty = y, tm = m, td = d;
    for (int i = 3; i >= 0; i--) dy[i] = ty % 10, ty /= 10;
    for (int i = 1; i >= 0; i--) dm[i] = tm % 10, tm /= 10;
    for (int i = 1; i >= 0; i--) dd[i] = td % 10, td /= 10;
    if (dy[0] != dd[1] || dy[1] != dd[0] || dy[2] != dm[1] || dy[3] != dm[0]) return false;
    return true;
}

int main()
{
    cin >> date1 >> date2;
    y = date1 / 10000, m = date1 % 10000 / 100, d = date1 % 100;
    gy = date2 / 10000, gm = date2 % 10000 / 100, gd = date2 % 100;
    while (true)
    {
        if (y == gy && m == gm && d == gd)
        {
            if (check()) res++;
            break;
        }
        if (check()) res++;
        d++;
        if (d > 28)
        {
            if (m == 2 && d > 28)
            {
                if (((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) && d > 29) m++, d %= 29;
                if (!((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) && d > 28) m++, d %= 28;
            }
            else if ((m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10 || m == 12) && d > 31) m++, d %= 31;
            else if ((m == 4 || m == 6 || m == 9 || m == 11) && d > 30) m++, d %= 30;
            if (m > 12) y++, m %= 12;
        }
    }
    cout << res << endl;
    return 0;
}

【枚举回文串代码】

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

int date1, date2, res;
int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };//每个月的天数

bool check(int date)
{
    int year = date / 10000, month = date % 10000 / 100, day = date % 100;
    if (month < 1 || month > 12 || day < 1) return false;
    if (month != 2)
    {
        if (day > days[month]) return false;
        return true;
    }
    int leap = year % 100 && year % 4 == 0 || year % 400 == 0;
    if (day > 28 + leap) return false;//平年时leap为0,闰年时为1
    return true;
}

int main()
{
    cin >> date1 >> date2;
    for (int i = 1000; i < 10000; i++)//枚举年份
    {
        int date = i, x = i;//date表示将年份扩充成回文日期的结果
        for (int j = 0; j < 4; j++) date = date * 10 + x % 10, x /= 10;
        if (date >= date1 && date <= date2 && check(date)) res++;
    }
    cout << res << endl;
}

六、AcWing 787. 归并排序

【题目描述】
给定你一个长度为 n n n的整数数列。
请你使用归并排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。

【输入格式】
输入共两行,第一行包含整数 n n n
第二行包含 n n n个整数(所有整数均在 1 ∼ 1 0 9 1\sim 10^9 1109范围内),表示整个数列。

【输出格式】
输出共一行,包含 n n n个整数,表示排好序的数列。

【数据范围】
1 ≤ n ≤ 100000 1≤n≤100000 1n100000

【输入样例】

5
3 1 2 4 5

【输出样例】

1 2 3 4 5

【代码】

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

const int N = 100010;
int a[N], tmp[N];
int n;

void merge_sort(int a[], int l, int r)
{
    if (l >= r) return;
    int mid = l + r >> 1;
    merge_sort(a, l, mid);
    merge_sort(a, mid + 1, r);
    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (a[i] <= a[j]) tmp[k++] = a[i++];
        else tmp[k++] = a[j++];
    while (i <= mid) tmp[k++] = a[i++];
    while (j <= r) tmp[k++] = a[j++];
    for (int i = l, j = 0; i <= r; i++, j++) a[i] = tmp[j];
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++) cin >> a[i];
    merge_sort(a, 0, n - 1);
    for (int i = 0; i < n; i++) cout << a[i] << ' ';
    return 0;
}

七、AcWing 1219. 移动距离

【题目描述】
X星球居民小区的楼房全是一样的,并且按矩阵样式排列。
其楼房的编号为 1 , 2 , 3 … 1,2,3\dots 1,2,3
当排满一行时,从下一行相邻的楼往反方向排号。

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

1  2  3  4  5  6
12 11 10 9  8  7
13 14 15 ...

我们的问题是:已知了两个楼号 m m m n n n,需要求出它们之间的最短移动距离(不能斜线方向移动)。

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

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

【数据范围】
1 ≤ w , m , n ≤ 10000 1≤w,m,n≤10000 1w,m,n10000

【输入样例】

6 8 2

【输出样例】

4

【分析】


首先我们先将所有的标号减一,变为如下的形式:

0  1  2  3  4  5
11 10 9  8  7  6
12 13 14 15 ...

可以观察到标号为 n n n时其二维坐标的行号为 n / w n/w n/w,如果不存在反向排号,则其列号为 n % w n\% w n%w。如果 n n n所在的行为奇数行,那么其列号应该反向排列,即列号为 w − 1 − n % w w-1-n\% w w1n%w。那么我们就可以在 O ( 1 ) O(1) O(1)的时间求出任何一个标号所在的行号与列号。


【代码】

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

int main()
{
    int w, n, m;
    cin >> w >> n >> m;
    n--, m--;
    int x1 = n / w, x2 = m / w, y1 = n % w, y2 = m % w;
    if (x1 % 2) y1 = w - 1 - y1;
    if (x2 % 2) y2 = w - 1 - y2;
    cout << abs(x1 - x2) + abs(y1 - y2) << endl;
    return 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 0\sim 9 09之间的数字(不一定相同)组成。

【输出格式】
输出若干个不相同的日期,每个日期一行,格式是YYYY-MM-DD
多个日期按从早到晚排列。

【数据范围】
0 ≤ A , B , C ≤ 9 0≤A,B,C≤9 0A,B,C9

【输入样例】

02/03/04

【输出样例】

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

【分析】


枚举1960年1月1日至2059年12月31日的所有日期,对于每个日期,判断其是否合法且是否有一种表示形式与所给形式相同,如果两者都满足那么直接输出当前日期。由于是从小到大枚举日期,因此输出的结果一定是从早到晚排列的。


【代码】

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

int a, b, c;
int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

bool check(int year, int month, int day)
{
    year %= 100;
    if (month < 1 || month > 12 || day < 1) return false;
    if (month != 2)
    {
        if (day > days[month]) return false;
    }
    else
    {
        int leap = year % 100 && year % 4 == 0 || year % 400 == 0;
        if (day > 28 + leap) return false;
    }
    if (year == a && month == b && day == c) return true;//年/月/日
    if (month == a && day == b && year == c) return true;//月/日/年
    if (day == a && month == b && year == c) return true;//日/月/年
    return false;
}

int main()
{
    scanf("%d/%d/%d", &a, &b, &c);
    for (int i = 19600101; i <= 20591231; i++)
    {
        int year = i / 10000, month = i % 10000 / 100, day = i % 100;
        if (check(year, month, day)) printf("%d-%02d-%02d\n", year, month, day);
    }
    return 0;
}

九、AcWing 1231. 航班时间

【题目描述】
h h h前往美国参加了蓝桥杯国际赛。
h h h的女朋友发现小 h h h上午十点出发,上午十二点到达美国,于是感叹到“现在飞机飞得真快,两小时就能到美国了”。
h h h对超音速飞行感到十分恐惧。
仔细观察后发现飞机的起降时间都是当地时间。
由于北京和美国东部有 12 12 12小时时差,故飞机总共需要 14 14 14小时的飞行时间。
不久后小 h h h的女朋友去中东交换。
h h h并不知道中东与北京的时差。
但是小 h h h得到了女朋友来回航班的起降时间。
h h h想知道女朋友的航班飞行时间是多少。
对于一个可能跨时区的航班,给定来回程的起降时间。
假设飞机来回飞行时间相同,求飞机的飞行时间。

【输入格式】
一个输入包含多组数据。
输入第一行为一个正整数 T T T,表示输入数据组数。
每组数据包含两行,第一行为去程的起降时间,第二行为回程的起降时间。
起降时间的格式如下:

  • h1:m1:s1 h2:m2:s2
  • h1:m1:s1 h3:m3:s3 (+1)
  • h1:m1:s1 h4:m4:s4 (+2)

第一种格式表示该航班在当地时间 h 1 h1 h1 m 1 m1 m1 s 1 s1 s1秒起飞,在当地时间当日 h 2 h2 h2 m 2 m2 m2 s 2 s2 s2秒降落。
第二种格式表示该航班在当地时间 h 1 h1 h1 m 1 m1 m1 s 1 s1 s1秒起飞,在当地时间次日 h 2 h2 h2 m 2 m2 m2 s 2 s2 s2秒降落。
第三种格式表示该航班在当地时间 h 1 h1 h1 m 1 m1 m1 s 1 s1 s1秒起飞,在当地时间第三日 h 2 h2 h2 m 2 m2 m2 s 2 s2 s2秒降落。

【输出格式】
对于每一组数据输出一行一个时间hh:mm:ss,表示飞行时间为 h h hh hh小时 m m mm mm s s ss ss秒。
注意,当时间为一位数时,要补齐前导零,如三小时四分五秒应写为03:04:05

【数据范围】
保证输入时间合法( 0 ≤ h ≤ 23 , 0 ≤ m , s ≤ 59 0≤h≤23,0≤m,s≤59 0h23,0m,s59),飞行时间不超过 24 24 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

【分析】


假设飞机飞行时间为 t i m e time time,从 a a a飞到 b b b的时差为 + d +d +d,即总共花费的时间为 t i m e + d time+d time+d,从 b b b飞回 a a a的时差为 − d -d d,即总共花费的时间为 t i m e − d time-d timed。那么往返两次的花费时间相加为 t i m e + d + t i m e − d = 2 ∗ t i m e time+d+time-d=2*time time+d+timed=2time,因此时差在此处并没有影响,两次的往返时间之差的和除以 2 2 2即为飞行时间。

本题的难点在于字符串的处理,由于输入的每一行字符串格式可能不一样,因此如果末尾没有(+1)之类的标注我们默认将其改为(+0),这样就可以将字符串统一格式。将每个串中的数字抽取出来可以使用sscanf,具体使用方式见代码。


【代码】

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

int n;
string line;

int get_second(int h, int m, int s)
{
    return h * 3600 + m * 60 + s;
}

int get_time()
{
    getline(cin, line);
    if (line.back() != ')') line += " (+0)";
    int h1, m1, s1, h2, m2, s2, day;
    sscanf(line.c_str(), "%d:%d:%d %d:%d:%d (+%d)", &h1, &m1, &s1, &h2, &m2, &s2, &day);
    return get_second(h2, m2, s2) - get_second(h1, m1, s1) + day * 86400;
}

int main()
{
    scanf("%d", &n);
    getline(cin, line);
    while (n--)
    {
        int time = (get_time() + get_time()) / 2;
        int h = time / 3600, m = time % 3600 / 60, s = time % 60;
        printf("%02d:%02d:%02d\n", h, m, s);
    }
    return 0;
}

十、AcWing 1241. 外卖店优先级

【题目描述】
“饱了么”外卖系统中维护着 N N N家外卖店,编号 1 ∼ N 1\sim N 1N
每家外卖店都有一个优先级,初始时( 0 0 0时刻)优先级都为 0 0 0
每经过 1 1 1个时间单位,如果外卖店没有订单,则优先级会减少 1 1 1,最低减到 0 0 0;而如果外卖店有订单,则优先级不减反加,每有一单优先级加 2 2 2
如果某家外卖店某时刻优先级大于 5 5 5,则会被系统加入优先缓存中;如果优先级小于等于 3 3 3,则会被清除出优先缓存。
给定 T T T时刻以内的 M M M条订单信息,请你计算 T T T时刻时有多少外卖店在优先缓存中。

【输入格式】
第一行包含 3 3 3个整数 N , M , T N,M,T N,M,T
以下 M M M行每行包含两个整数 t t t i d id id,表示 t t t时刻编号 i d id id的外卖店收到一个订单。

【输出格式】
输出一个整数代表答案。

【数据范围】
1 ≤ N , M , T ≤ 1 0 5 1≤N,M,T≤10^5 1N,M,T105
1 ≤ t ≤ T 1≤t≤T 1tT
1 ≤ i d ≤ N 1≤id≤N 1idN

【输入样例】

2 6 6
1 1
5 2
3 1
6 2
2 1
6 2

【输出样例】

1

【样例解释】
6 6 6时刻时, 1 1 1号店优先级降到 3 3 3,被移除出优先缓存; 2 2 2号店优先级升到 6 6 6,加入优先缓存。
所以是有 1 1 1家店( 2 2 2号)在优先缓存中。

【分析】


  1. 首先对输入的 m m m个订单信息排序(时间 t t t为第一优先级,订单 i d id id为第二优先级)。
  2. 遍历订单信息(记得此时订单大体是按照时间顺序排的)。
  3. 假设当前订单为第 i i i个,循环判断后面有没有相同的订单,即 t t t i d id id都相等(有的话则这些订单一定连续)。
  4. 当到第 j j j个时订单不相同,此时相同订单的数量为 c n t = j − i cnt=j-i cnt=ji,下一次循环从 j j j处开始遍历。
  5. 记录此时的时刻 t t t和店铺 i d id id,计算 i d id id的优先权,有两部分:
    (1)上一个拿到订单的时间 l a s t [ i d ] last[id] last[id] t t t之间,中间没订单所以要 − 1 -1 1,没订单的数量是 t − l a s t [ i ] − 1 t-last[i]-1 tlast[i]1(比如第 3 3 3和第 6 6 6时刻都有订单,没有订单的时候就是 4 , 5 4,5 4,5),然后计算优先权,如果为负值更新为 0 0 0。如果小于等于 3 3 3,则更新优先缓存 s t [ i d ] = f a l s e st[id]=false st[id]=false
    (2)此时, t t t时刻拿到订单,并且拿到的数量为 c n t cnt cnt,优先级要加上 2 ∗ c n t 2*cnt 2cnt,然后计算优先权,如果大于 5 5 5,则更新优先缓存 s t [ i d ] = t r u e st[id]=true st[id]=true
  6. 解释上面那个,因为此时这几个相同的订单都计算过了不需要再计算了,所以下一次循环要从 j j j开始。
  7. 循环最后,店铺 i d id id上一次拿到订单的时间 l a s t [ i d ] last[id] last[id]更新为 t t t
  8. 如果最后一个订单时刻为 T T T,则没问题。如果不是 T T T,那么最后一个拿到订单时刻到 T T T时刻的这部分减法需要手动计算,即优先级需要减去 T T T时刻与该店最后一个订单时刻 l a s t [ i d ] last[id] last[id]的差值。换而言之,如果上一个拿到订单的时间 l a s t [ i d ] last[id] last[id]小于 T T T,则优先权减去 T − l a s t [ i d ] T-last[id] Tlast[id]。注意这里不减 1 1 1,因为 T T T时刻也没订单。如果小于等于 3 3 3,则更新优先缓存 s t [ i d ] = f a l s e st[id]=false st[id]=false

【代码】

#include <iostream>
#include <cstring>
#include <algorithm>
#define X first
#define Y second
using namespace std;

typedef pair<int, int> PII;
const int N = 100010;
int score[N], last[N];//score[i]表示第i个店铺当前的优先级,last[i]表示第i个店铺上一次有订单的时刻
PII order[N];//表示所有订单
bool st[N];//st[i]表示第i个店铺是否处于优先缓存中
int n, m, T;

int main()
{
    scanf("%d%d%d", &n, &m, &T);
    for (int i = 0; i < m; i++) scanf("%d%d", &order[i].X, &order[i].Y);
    sort(order, order + m);//将订单按照时间从小到大排序
    for (int i = 0; i < m;)
    {
        int j = i;
        while (j < m && order[i] == order[j]) j++;//找出一批相同的订单一起处理
        int t = order[i].X, id = order[i].Y, cnt = j - i;
        i = j;
        score[id] -= t - last[id] - 1;//t时刻有订单因此需要减一
        if (score[id] < 0) score[id] = 0;
        if (score[id] <= 3) st[id] = false;
        score[id] += cnt * 2;//将t时刻的订单算上
        if (score[id] > 5) st[id] = true;
        last[id] = t;//更新last
    }
    for (int i = 1; i <= n; i++)
        if (last[i] < T)//在最后一段时间内没收到订单
        {
            score[i] -= T - last[i];//T时刻没有订单因此不需要减一
            if (score[i] <= 3) st[i] = false;
        }
    int res = 0;
    for (int i = 1; i <= n; i++) res += st[i];
    printf("%d\n", res);
    return 0;
}

十一、AcWing 788. 逆序对的数量

【题目描述】
给定一个长度为 n n n的整数数列,请你计算数列中的逆序对的数量。
逆序对的定义如下:对于数列的第 i i i个和第 j j j个元素,如果满足 i < j i<j i<j a [ i ] > a [ j ] a[i]>a[j] a[i]>a[j],则其为一个逆序对;否则不是。

【输入格式】
第一行包含整数 n n n,表示数列的长度。
第二行包含 n n n个整数,表示整个数列。

【输出格式】
输出一个整数,表示逆序对的个数。

【数据范围】
1 ≤ n ≤ 100000 1≤n≤100000 1n100000
数列中的元素的取值范围 [ 1 , 1 0 9 ] [1,10^9] [1,109]

【输入样例】

6
2 3 4 5 6 1

【输出样例】

5

【分析】


在归并排序的合并过程中,两个数组内的元素都为有序状态,因此若左半部分数组中的第一个元素 a [ i ] a[i] a[i]大于右半部分数组的第一个元素 a [ j ] a[j] a[j],说明 a [ i ] a[i] a[i]以及其后面的所有元素都是 a [ j ] a[j] a[j]的逆序对(因为左半部分数组内部是有序的, a [ i ] a[i] a[i]之后的元素一定大于等于 a [ i ] a[i] a[i]),故可以利用此性质在归并排序的过程中求解逆序对的数量。


【代码】

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

typedef long long LL;
const int N = 100010;
int n, a[N], tmp[N];
LL res;

void merge_sort(int a[], int l, int r)
{
    if (l >= r) return;
    int mid = l + r >> 1;
    merge_sort(a, l, mid);
    merge_sort(a, mid + 1, r);
    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (a[i] <= a[j]) tmp[k++] = a[i++];
        else
        {
            res += mid - i + 1;
            tmp[k++] = a[j++];
        }
    while (i <= mid) tmp[k++] = a[i++];
    while (j <= r) tmp[k++] = a[j++];
    for (int i = l, j = 0; i <= r; i++, j++) a[i] = tmp[j];
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);
    merge_sort(a, 0, n - 1);
    printf("%lld", res);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

柃歌

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

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

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

打赏作者

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

抵扣说明:

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

余额充值