【PAT乙级】B1001-B1095刷题记录

文章目录

PTA题目

PTA的题目是单点测试,所以写法上会有不同。

中国大学MOOC-陈越、何钦铭-数据结构-起步能力自测题

自测-1 打印沙漏 (20 分)

Exercise
在这里插入图片描述Example
输入样例:
19 *
输出样例:

*****
 ***
  *
 ***
*****
2

这题最坑的是每行输出字符结束后就直接换行,不输出空白符,眼神不好的就会犯错然后格式错误。

#include <cstdio>

int main()
{
    int N, begin = 1, symbolUse = begin;
    char c;
    scanf("%d %c", &N, &c);
	while (symbolUse <= N) {
		begin += 2;
		symbolUse += begin * 2;	
	} 
	symbolUse -= begin *2; // 总共要使用的符号数
	begin -= 2;  // 第一层要用的符号数  
    for (int k = begin; k >= 1; k-=2) {
        int spaceNum = begin - k;
		for (int w = 1; w <= spaceNum / 2; w++)
			printf(" "); 
		for (int j = 1; j <= k; j++)
            printf("%c", c);
        printf("\n");
    }
    for (int k = 3; k <= begin; k+=2) {
    	int spaceNum = begin - k;
    	for (int w = 1; w <= spaceNum / 2; w++)
			printf(" "); 
        for (int j = 1; j <= k; j++)  
        	printf("%c", c);
		printf("\n");
    }
    printf("%d\n", N - symbolUse);
    return 0;
}

自测-2 素数对猜想 (20 分)

Exercise
让我们定义d​n​​为:d​n​​=p​n+1​​−p​n​​,其中p​i​​是第i个素数。显然有d​1​​=1,且对于n>1有d​n​​是偶数。“素数对猜想”认为“存在无穷多对相邻且差为2的素数”。现给定任意正整数N(<10​5​​),请计算不超过N的满足猜想的素数对的个数。
Example

输入样例:
20
输出样例:
4

My thoughts
我的想法是用筛法算出素数数组,然后一对对数。

#include <cstdio>
/* 素数对是指两个相邻素数间隔2,则2、3不是,3、5是 */
int main()
{
    int N;
    scanf("%d", &N);
    const int room = N+10;
    int isPrime[room];
    for (int i = 2; i <= N; i++) 
        isPrime[i] = 1; // all are prime.
    for (int i = 2; i <= N; i++) {
        if (isPrime[i]) {
            for (int j = i * 2; j <= N; j += i) 
                isPrime[j] = 0;
        }
    }
    int count = 0;
    for (int i = 2; i <= N-2; i++) {
        if (isPrime[i] && isPrime[i+2])
            count++;
    }
    printf("%d", count);
    return 0;
}

自测-3 数组元素循环右移问题 (20 分)

Exercise
一个数组A中存有N(>0)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(≥0)个位置,即将A中的数据由(A​0​​A​1​​⋯A​N−1​​)变换为(A​N−M​​⋯A​N−1​​A​0​​A​1​​⋯A​N−M−1​​)(最后M个数循环移至最前面的M个位置)。如果需要考虑程序移动数据的次数尽量少,要如何设计移动的方法?
Example

输入格式:
每个输入包含一个测试用例,第1行输入N(1≤N≤100)和M(≥0);第2行输入N个整数,之间用空格分隔。
输出格式:
在一行中输出循环右移M位以后的整数序列,之间用空格分隔,序列结尾不能有多余空格。
输入样例:
6 2
1 2 3 4 5 6
输出样例:
5 6 1 2 3 4

My thoughts
这个题还有点奇怪,暴力的话可以直接用一个存储空间,将N个数不断向后移动一位,移动M次,时间复杂度为O(M * N)。缩小一下M的话可以求M = M Mod N。不过最好的还是以下的做法,这类的题目都可以这样做。

假设原数组序列为abcd1234,要求变换成的数组序列为1234abcd,即循环右移了4位。比较之后,不难看出,其中有两段的顺序是不变的:1234和abcd,可把这两段看成两个整体。右移K位的过程就是把数组的两部分交换一下。变换的过程通过以下步骤完成:

  1. 逆序排列abcd:abcd1234 → dcba1234;
  2. 逆序排列1234:dcba1234 → dcba4321;
  3. 全部逆序:dcba4321 → 1234abcd。
#include <cstdio>
void reverse(int a[], int left, int right) {
    // for (int i = left; i < (left + right + 1) / 2; i++) {
    //     int temp = a[i];
    //     a[i] = a[right-i];
    //     a[right-i] = temp;
    // } 这样翻转写起来麻烦
    for (int i = left, j = right; i < j; i++, j--) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}
int main()
{
    int N, M;
    scanf("%d%d", &N, &M);
    int a[N];
    for (int i = 0; i < N; i++) {
        scanf("%d", &a[i]);
    }
    // 有O(M*N)的方法, 也有以下的方法
    M = M % N;
   	reverse(a, 0, N-M-1);
    reverse(a, N-M, N-1);
    reverse(a, 0, N-1);
   	for (int i = 0; i < N-1; i++)
    	printf("%d ", a[i]);
    printf("%d", a[N-1]);
    return 0;
}

自测-4 Have Fun with Numbers (20 分)

这一道题目有一些值得我反思的地方,要详细分析一下。位数溢出,连long long类型都超过了,只好使用字符数组模拟。
https://blog.csdn.net/royzdr/article/details/78902766.
1.

#include <cstdio>
#include <cstring>
int main()
{
    char s[30], db_s[30];
    scanf("%s", s);
   
    char *p = s;
    int a[10], b[10];  
    memset(a, 0, sizeof(a));
    memset(b, 0, sizeof(b));
	
    while (*p) {
        a[(*p - '0') % 10]++;
        p++;
    }

    int le = strlen(s), carry = 0, j = 0; // 进位的数字
    for (int i = le-1; i >= 0; i--, j++) {
        int r = (s[i] - '0') * 2 + carry;
        carry = r / 10;
        db_s[j] = (char)('0' + r % 10);
        b[r % 10]++;
    }
    if (carry) {
        db_s[j] = (char)('0' + carry);
        db_s[j+1] = '\0';
    } else db_s[j] = '\0';
    
    int YesNo = 1;
    for (int k = 0; k < 10; k++) {
        if (a[k] != b[k]) {
            YesNo = 0;
            break;
        }
    }
    
    int len = strlen(db_s);
    if (YesNo) {
    	printf("Yes\n");
    	for (int i = len-1; i >= 0; i--, j++) 
	        printf("%c", db_s[i]);
	}
    else {
        printf("No\n");
        for (int i = len-1; i >= 0; i--, j++) 
	        printf("%c", db_s[i]);
    }
    return 0;
}

自测-5 Shuffling Machine (20 分)

这是一个自动洗牌机,牌初始的顺序我直接写在了代码里面,然后按照给定的顺序将牌的位置变为指定位置,并且对所有牌执行K次。If the number at the i-th position is j, it means to move the card from position i to position j,将位于第i项的牌移动到给定位置去,特别注意:在操作牌号数组时牌的编号要减1。

一开始我想了蛮久,(ˇˍˇ) 想直接在字符串数组内部移动,后来发现可能有环路,很麻烦,然后我发现,这种操作是将一张牌的位置改变为指定位置,干脆直接开一个临时字符串数组,每次将cards[i]移动到temp[rotate[i]]中,完成后再复制回来。就这么简单。

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

int main() {
    int K;
    scanf("%d", &K);
    string cards[54] = {
     "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9", "S10", "S11", "S12", "S13",
	 "H1", "H2", "H3", "H4", "H5", "H6", "H7", "H8", "H9", "H10", "H11", "H12", "H13",
	 "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10", "C11", "C12", "C13", 
	 "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10", "D11", "D12", "D13",
	 "J1", "J2"};
    int rotate[54];
    string temp[54];
    for (int i = 0; i < 54; i++) {
    	scanf("%d", &rotate[i]);
    	rotate[i] = rotate[i] - 1;
	}
    while (K--) {
        for (int i = 0; i < 54; i++) {
        	temp[rotate[i]] = cards[i]; 
		}
		for (int i = 0; i < 54; i++) {
			cards[i] = temp[i];
		}
	}
	for (int i = 0; i < 54; i++) {
		if (i > 0) cout << " ";
		cout << cards[i];
	}
		
    return 0;
}

PAT (Basic Level) Practice (中文)

B1001 害死人不偿命的(3n+1)猜想 (15 分)

#include <cstdio>
int main()
{
    int n, step = 0;
    scanf("%d", &n); //输入题目给的n
    while (n != 1) { //n不等于时循环
        if (n % 2) n = (3 * n + 1) / 2; //是奇数
        else n /= 2;  //是偶数
        step++;  // 计数器加1
    }
    printf("%d\n", step);
    return 0;
}

B1002 写出这个数 (20 分)

读入一个正整数 n,计算其各位数字之和,用汉语拼音写出和的每一位数字。
输入格式:每个测试输入包含 1 个测试用例,即给出自然数n的值。这里保证n小于10​100​​。
输出格式:在一行内输出 n 的各位数字之和的每一位,拼音数字间有 1 空格,但一行中最后一个拼音数字后没有空格

这道题输入的数字太大,必须用字符串。且输出格式有点要求,可以像我下面这么做。

#include <cstdio>
#include <cstring>
int main()
{
    char s[120];
    scanf("%s", s);
    int len = strlen(s); 
    char r[][6] = {
        "ling", "yi", "er", "san", "si", 
        "wu", "liu", "qi", "ba", "jiu"
    };
    int carry_sum = 0; // 每位数的和不大
    for (int i = 0; i < len; i++) 
        carry_sum += (s[i] - '0');
    int a[5], t = 0; 
    while (carry_sum) { //将sum中的每一位存到数组中, 顺位存储
        a[t] = carry_sum % 10;
        carry_sum /= 10;
        t++;
    }
    printf("%s", r[a[t - 1]]); // 第一个拼音
    for (int i = t - 2; i >= 0; i--) 
        printf(" %s", r[a[i]]);
    printf("\n");
    return 0;
}

☆ B1003 我要通过! (20 分)

有难度。

#include <cstdio>
#include <cstring>
int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        char s[120];
        scanf("%s", s);
        int len = strlen(s);
        int num_p = 0, num_t = 0, other = 0; //代表P的个数、T的个数、除PAT外字符的个数
        int loc_p = -1, loc_t = -1; //分别代表P的位置、T的位置
        for (int i = 0; i < len; i++) {
            if (s[i] == 'P') { //当前字符为P
                num_p++;   //个数+1
                loc_p = i; //位置变为i
            } else if (s[i] == 'T') { //当前字符为T
                num_t++;   //个数+1
                loc_t = i; //位置变为i
            } else if (s[i] != 'A') other++; //除PAT外字符的个数+1
        }
        //如果P的个数不为1; 或者T的个数不为1; 或者存在除PAT之外的字符; 或者P和T之间没有字符
        if (num_p != 1 || num_t != 1 || other != 0 || loc_t - loc_p <= 1) {
            printf("NO\n"); continue;
        } 
        int x = loc_p, y = loc_t - loc_p - 1, z = len - loc_t - 1;
        if (z - x * (y - 1) == x) { //条件2成立的思路
            printf("YES\n");
        } else printf("NO\n");
    }
    return 0;
}

B1004 成绩排名 (20 分)

这个题目说简单也简单,只要利用结构体变量间可以赋值的事实就可以很快做出来。
这里我令结构体变量stu记录临时存放的数据,max、min则存放成绩最高和最低的学生信息。首先将max、min初始化,可以是-1(max)和101(min)(毕竟这里成绩为0-100);或者像我一样干脆初始化为第一个学生的信息。然后一边录入一边更新,Online算法。这里注意的是比较新学生和max、min的成绩时不会同时更新两个,因此可以用if-else(if),也可以都用if

如果采用结构体数组,至少要101的大小,因为题目中说不会出现相同的成绩。这样浪费了空间。如果还进行排序的话,就连时间也浪费了。因此不推荐这两种方法。

B1028和这道题很像,但是更难一点。

#include <cstdio>
struct student {
    char name[15];
    char id[15];
    int score;
}stu, max, min;

int main()
{
    int n;
    scanf("%d", &n);
    scanf("%s %s %d", stu.name, stu.id, &stu.score);
    max = min = stu;
    for (int i = 1; i < n; i++) {
        scanf("%s %s %d", stu.name, stu.id, &stu.score);
        if (stu.score > max.score) max = stu;
        if (stu.score < min.score) min = stu;
    }
    printf("%s %s\n%s %s\n", max.name, max.id, min.name, min.id);
    
    return 0;
}

B1005 继续(3n+1)猜想 (25 分)

卡拉兹(Callatz)猜想已经在1001中给出了描述。在这个题目里,情况稍微有些复杂。当我们验证卡拉兹猜想的时候,为了避免重复计算,可以记录下递推过程中遇到的每一个数。例如对n=3进行验证的时候,我们需要计算3、5、8、4、2、1,则当我们对n=5、8、4、2进行验证的时候,就可以直接判定卡拉兹猜想的真伪,而不需要重复计算,因为这4个数已经在验证3的时候遇到过了,我们称5、8、4、2是被3“覆盖”的数我们称一个数列中的某个数n为“关键数”,如果n不能被数列中的其他数字所覆盖

现在给定一系列待验证的数字,我们只需要验证其中的几个关键数,就可以不必再重复验证余下的数字。你的任务就是找出这些关键数字,并按从大到小的顺序输出它们。

  • 输入格式:每个测试输入包含 1 个测试用例,第 1 行给出一个正整数 K (<100),第 2 行给出 K 个互不相同的待验证的正整数 n (1<n≤100)的值,数字间用空格隔开。
  • 输出格式:每个测试用例的输出占一行,按从大到小的顺序输出关键数字。数字间用 1 个空格隔开,但一行中最后一个数字后没有空格。
  • 输入样例:
    6
    3 5 6 7 8 11
    
  • 输出样例:
    7 6
    

这里有几个地方要注意:

  • 段错误:hash数组开得太小了!或者可以判断计算中的每一个数,如果大于100就不操作。
  • 把题目读懂,概念理解清楚,就没有太多难度。
#include <cstdio>
#include <algorithm>
using namespace std;
bool cmp(int a, int b) { return a > b; }
int hash[10500] = {0};
/* 如果n不能被数列中的其他数字所覆盖, 则n为“关键数”, 即数列中其他数计算时不会计算到它 */
void Callatz(int n) {
    while (n != 1) { //n不等于时循环
        if (n % 2) n = (3 * n + 1) / 2; //是奇数
        else n /= 2;  //是偶数
        hash[n] = 0; //在计算某一个数的卡拉兹数列时被覆盖
    }
}

int main() {
    int K, cnt = 0;
    scanf("%d", &K); 
    int a[K];
    for (int i = 0; i < K; i++) { scanf("%d", &a[i]); hash[a[i]] = 1; }
    sort(a, a + K, cmp); //从大到小排序
    for (int i = 0; i < K; i++) Callatz(a[i]); 
    for (int i = 0; i < K; i++) {
        if (hash[a[i]] != 0) { 
            if (cnt == 0) { printf("%d", a[i]); cnt++; }
            else printf(" %d", a[i]);
        }
    }
    return 0;
}

B1006 换个格式输出整数 (15 分)

#include <cstdio>

int main() {
    char str[50];
    int n, size = 0, g, s, b;
    scanf("%d", &n);
    g = n % 10, s = n % 100 / 10, b = n / 100;
    for (int i = 1; i <= b; i++) str[size++] = 'B';
    for (int i = 1; i <= s; i++) str[size++] = 'S';
    for (int i = 1; i <= g; i++) str[size++] = i + '0';
    str[size] = '\0';
    printf("%s\n", str);

    return 0;
}

B1007 素数对猜想 (20 分)

按筛法解题,在上面做过的现题目,感觉上面写的还简单一点,不是生搬硬套:

#include <cstdio>
const int maxn = 100010;
int prime[maxn], p[maxn] = {0}, pNum = 0;
void findPrime(int n) { //大于n时退出
    for (int i = 2; i < maxn; i++) {
        if (i > n) break;
        if (p[i] == 0) {
            prime[pNum++] = i;
            for (int j = i + i; j < maxn; j += i) {
                p[j] = 1;
            }
        }
    }

}
int main() {
    int n;
    scanf("%d", &n);
    findPrime(n);
    int ans = 0; //素数对个数
    for (int i = 0; i < pNum - 1; i++) {
        if (prime[i + 1] - prime[i] == 2) ans++;
    }
    printf("%d\n", ans);
    return 0;
}

B1008 数组元素循环右移问题 (20 分)

在不允许使用另外数组的前提下。这道题也做过,就在上边,知道是用三步翻转法。可是竟然还会错,原因在于忘记翻转数组的时候下标必须合法, 对于M>=N的必须取余使之合法
因为本题并未给出M的最大值,不能直接认定M<N,而M对N取余后就可以保证M<N,使后面的操作更简单。依据在于长度为N的序列,在右移N位后与当前序列相同,因此取余为0可以省去移动的次数

#include <cstdio>
/* 将数组a的i到j位翻转[i, j] */
void reverseArray(int a[], int i, int j) 
{
    for (int left = i, right = j; left < right; left++, right--) {
        int temp = a[left];
        a[left] = a[right];
        a[right] = temp;
    }
}

int main()
{
    int N, M;
    scanf("%d%d", &N, &M);
    int a[N];
    for (int i = 0; i < N; i++) 
        scanf("%d", &a[i]);
    M = M % N; /* 翻转数组的时候下标必须合法, 对于M>N的必须取余使之合法 */
    reverseArray(a, 0, N - M - 1);
    reverseArray(a, N - M, N - 1);
    reverseArray(a, 0, N - 1);
    
    printf("%d", a[0]);
    for (int i = 1; i < N; i++) 
        printf(" %d", a[i]);
    return 0;
}

更加tricky的方法是,既然这种题目没有要求过程而只要结果,那我们就直接给它结果

#include <cstdio>
int main()
{
    int N, M;
    scanf("%d%d", &N, &M);
    int a[N];
    for (int i = 0; i < N; i++) 
        scanf("%d", &a[i]);
    M = M % N;
    //第一个for循环输出N-M到N-1号, 不可能将数组输出完, 不用处理最后一个数的格式
    for (int i = N - M; i < N; i++) 
        printf("%d ", a[i]);
    for (int i = 0; i < N - M - 1; i++) 
        printf("%d ", a[i]);
    printf("%d", a[N - M - 1]);  // 输出最后一个数
    return 0;
}

B1009 说反话 (20 分)

这题不知道为什么写成while (scanf("%s", s[len++]) != EOF);就死活不通过。以后还是按照下面写吧。
而且,我发现写成for (; scanf("%s", s[len]) != EOF; len++);这样也可以通过……

#include <cstdio>

int main()
{
    char s[100][100];
    int len = 0;
    while (scanf("%s", s[len]) != EOF) { //一直输入到文件末尾
        len++;
    }
    for (int i = len - 1; i >= 1; i--)  //倒着输出单词
        printf("%s ", s[i]);
    printf("%s", s[0]);
    return 0;
}

法2:将输入的字符串分割成单词

#include <stdio.h>
#include <string.h>

int main()
{
    char str[90], s[100][100]; //s存放单词

    gets(str);
    int len = strlen(str), r = 0, c = 0; //r行标 c列标
    for (int i = 0; i < len; i++) { 
        if (str[i] != ' ') {  //不是空格存放进s[r][c], c++
            s[r][c++] = str[i];
        } else {    //是空格, 说明一个单词结束, 行r+1, 列c置0
            s[r][c] = '\0';  //末尾是结束符'\0'
            r++;
            c = 0;
        }
    }
    for (int i = r; i >= 0; i--) { //倒着输出单词
        printf("%s", s[i]);
        if (i > 0) printf(" ");
    }
    return 0;
}

★★ B1010 一元多项式求导 (25 分)

从低次项至高次项进行枚举求导,通过求导公式修改数组的元素,同时计数系数不为0的导数项个数。

#include <cstdio>
int main()
{
    int coef[1010] = {0}; //对应指数e的系数数组
    int co, ef, cnt = 0; //系数 指数 不为0的导数项的个数
    while (scanf("%d%d", &co, &ef) != EOF) coef[ef] = co; 
    coef[0] = 0; //0次项求导后系数直接为0
    for (int i = 1; i <= 1000; i++) {
        coef[i - 1] = coef[i] * i; //求导公式, 从低次项向高次项枚举
        coef[i] = 0; //此句不可省略
        if (coef[i - 1] != 0) cnt++; //计数系数不为0的导数项个数
    }
    if (cnt == 0) printf("0 0");  //特判, 全部导数项系数为0
    else {
        for (int i = 1000; i >= 0; i--) { //按指数从高到低输出系数和指数
            if (coef[i] != 0) {
                printf("%d %d", coef[i], i);
                cnt--;
                if (cnt > 0) printf(" ");
            }
        }
    }
    return 0;
}

B1011 A+B和C(15)

CodeUp做过的一道题。
题目给的范围是[-231, 231], 需知道int的数据范围是[-231,231-1]。可能会溢出。必须使用long long类型作为ABC的变量类型,输入输出格式必须是"%lld"。

#include <cstdio>
int main()
{
    int T, tcase = 1;
    scanf("%d", &T);
    while (T--) {
        long long a, b, c;
        scanf("%lld%lld%lld", &a, &b, &c);
        if (a + b > c) printf("Case #%d: true\n", tcase++);
        else printf("Case #%d: true\n", tcase++);
    }
    return 0;
}

B1012 数字分类 (20 分)

CodeUp做过的一道题。
不过这里优化一下写法。在判断某类数字是否存在时使用数组count,count为0即不存在。对五类数字的结果使用ans数组。两者初值均为0。

#include <cstdio>

int main()
{
    int N, a;
    int ans[5] = {0}, count[5] = {0};
    scanf("%d", &N);	
    int flag = 1;
    for (int i = 0; i < N; i++) {
        scanf("%d", &a);
        switch (a % 5) { 
            case 0:   //A1类
                if (a % 2 == 0) {
                    ans[0] += a;
                    count[0]++;	
                }
                break;
            case 1:   //A2类
                if (flag) {
                    ans[1] += a;
                    count[1]++;
                    flag = 0;
                } else {
                    ans[1] += -a;
                    count[1]++;
                    flag = 1;
                } 
                break;
            case 2:   //A3类
                ans[2]++;
                count[2]++;
                break;
            case 3:   //A4类
                ans[3] += a;
                count[3]++;
                break;
            case 4:   //A5类
                if (a > ans[4]) 
                    ans[4] = a;
                count[4]++;
                break;
        } 
    }
    if (count[0]) printf("%d ", ans[0]);
    else printf("N ");
    if (count[1]) printf("%d ", ans[1]);
    else printf("N ");
    if (count[2]) printf("%d ", ans[2]);
    else printf("N ");
    if (count[3]) printf("%.1f ", (double)ans[3] / count[3]);
    else printf("N ");
    if (count[4]) printf("%d", ans[4]); //最后一个输出不能有空格
    else printf("N"); 
    return 0;
}

B1013 数素数 (20 分)

这一题只要求输出第m到第n个素数,超过n个素数之后的就不用保存了,因此添加了控制语句。而考虑到不知道第104个素数有多大,因此设置测试上限设得大了一些

#include <cstdio>
const int maxn = 1000001;
int prime[maxn] = {0}, pNum = 0;
bool p[maxn] = {0};
void find_prime(int n) { //找到第n个素数后退出
    for (int i = 2; i < maxn; i++) {
        if (p[i] == false) { //说明它是素数
            prime[pNum++] = i;
            if (pNum >= n) break; //只需要n个素数, 超过时就可以结束
            for (int j = i + i; j < maxn; j += i) {
                p[j] = true;
            }
        }
    }
}

int main() {
    int m, n, cnt = 0;
    scanf("%d%d", &m, &n);
    find_prime(n);
    for (int i = m - 1; i <= n - 1; i++) {
        printf("%d", prime[i]); //下标从0开始
        cnt++;
        if (cnt % 10 != 0 && i < n - 1) printf(" ");
        else printf("\n");
    }
    return 0;
}

★ B1014 福尔摩斯的约会 (20 分)

大侦探福尔摩斯接到一张奇怪的字条:我们约会吧! 3485djDkxh4hhGE 2984akDfkkkkggEdsb s&hgsfdk d&Hyscvnm。大侦探很快就明白了,字条上奇怪的乱码实际上就是约会的时间星期四 14:04,因为前面两字符串中第1对相同的大写英文字母大小写有区分)是第4 个字母D,代表星期四第2对相同的字符是E,那是第5个英文字母,代表一天里的第14个钟头(于是一天的0点到23点由数字0到9、以及大写字母A到N表示);后面两字符串第1对相同的英文字母s出现在第4个位置(从0开始计数)上,代表第4分钟。现给定两对字符串,请帮助福尔摩斯解码得到约会的时间。

  • 输入格式:输入在 4 行中分别给出 4 个非空、不包含空格、且长度不超过 60 的字符串。
  • 输出格式:在一行中输出约会的时间,格式为 DAY HH:MM,其中 DAY 是某星期的 3 字符缩写,即 MON 表示星期一,TUE 表示星期二,WED 表示星期三,THU 表示星期四,FRI 表示星期五,SAT 表示星期六,SUN 表示星期日。题目输入保证每个测试存在唯一解。
  • 输入样例:
    3485djDkxh4hhGE 
    2984akDfkkkkggEdsb 
    s&hgsfdk 
    d&Hyscvnm
    
  • 输出样例:
    THU 14:04
    

这道题还是有一些要注意的地方的:

  • 前两对字符串中判断字符需要完整的限定条件,判断星期几为A-G,判断第几个钟头需要0-9、A-N,不能出现A-Z;
  • 前两对字符串中还要区分是星期或者时钟得到小时后立刻退出,避免被后面相同的字符更新;
  • 同样,后两对字符串中得到分钟数后立刻退出。
#include <cstdio>
char weekday[][6] = {"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};

int main() {
    char s[4][70];
    for (int i = 0; i < 4; i++) scanf("%s", s[i]);
    int i, day, h, m, cnt = 1;
    for (i = 0; s[0][i] && s[1][i]; i++) {
        if (cnt == 1 && s[0][i] >= 'A' && s[0][i] <= 'G' && s[0][i] == s[1][i]) { day = s[0][i] - 'A'; cnt++; }
        else if (cnt == 2 && (s[0][i] >= '0' && s[0][i] <= '9' || s[0][i] >= 'A' && s[0][i] <= 'N') && s[0][i] == s[1][i]) { 
            h = s[0][i] >= 'A' ? s[0][i] - 'A' + 10 : s[0][i] - '0'; break; 
        }
    }
    for (i = 0; s[2][i] && s[3][i]; i++) {
        if ((s[2][i] >= 'A' && s[2][i] <= 'Z' || s[2][i] >= 'a' && s[2][i] <= 'z') && s[2][i] == s[3][i]) { m = i; break; }
    }
    printf("%s %02d:%02d\n", weekday[day], h, m);
    return 0;
}

★ B1015 德才论 (25 分)

写排序函数的经典题目。这一题中,没有达到合格标准的考生,我都没有存到结构体数组中去,毕竟多写几句,排序的规模说不定小一些。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct student {
    char id[10]; 
    int d, c, sum;
    int type; //类别
} stu[100100];
bool cmp(struct student a, struct student b) { 
    if (a.type != b.type) return a.type < b.type; //类别小的在前
    else if (a.sum != b.sum) return a.sum > b.sum; //类别相同时总分大的在前
    else if (a.d != b.d) return a.d > b.d; //总分相同时德分大的在前
    else return strcmp(a.id, b.id) < 0; //德分相同时按准考证号从小到大排序
}
int main() {
    int N, L, H, legalNum = 0;
    scanf("%d%d%d", &N, &L, &H);
    struct student temp;
    for (int i = 0; i < N; i++) {
        scanf("%s%d%d", temp.id, &temp.d, &temp.c);
        temp.sum = temp.d + temp.c;
        if (temp.d < L || temp.c < L) continue;
        else if (temp.d >= H && temp.c >= H) {
            temp.type = 1;    //德分和才分均不低于此线的为才德全尽
            stu[legalNum++] = temp;
        } else if (temp.d >= H && temp.c < H) {
            temp.type = 2;    //才分不到但德分到线的一类考生属于德胜才       
            stu[legalNum++] = temp;
        } else if (temp.d < H && temp.c < H && temp.d >= temp.c) {
            temp.type = 3;    // 德才分均低于H,但是德分不低于才分的考生属于才德兼亡但尚有德胜才
            stu[legalNum++] = temp;
        } else {
            temp.type = 4;    //其他达到最低线L的考生也按总分排序      
            stu[legalNum++] = temp;
        }
    }
    sort(stu, stu + legalNum, cmp);
    printf("%d\n", legalNum);
    for (int i = 0; i < legalNum; i++) {
        printf("%s %d %d\n", stu[i].id, stu[i].d, stu[i].c);
    }
    return 0;
}

B1016 部分A+B(15)

我在CodeUp使用的是字符串存储,枚举字符串中出现该字符的数码,然后恢复成数字。这里我用的方法更简单,只要用12行就可以了。使用long long存放A和B,枚举A和B中的每一位,如果该位恰好等于DA,就让PA = PA * 10 + DA

#include <cstdio>

int main()
{
    long long a, b, da, db;
    scanf("%lld%lld%lld%lld", &a, &da, &b, &db);
    long long pa = 0, pb = 0;
    while (a) { // 枚举a的每一位
        if (a % 10 == da) pa = pa * 10 + da;
        a /= 10;
    }
    while (b) { // 枚举b的每一位
        if (b % 10 == db) pb = pb * 10 + db;
        b /= 10;
    }
    printf("%lld\n", pa + pb);
    return 0;
}

B1017 A除以B (20 分)

这道题为高精度整数与低精度整数的除法操作,可以直接使用书中的模版。

#include <cstdio>
#include <cstring>
const int maxn = 1010;
struct bign {
    int d[maxn], len;
    bign() {
        memset(d, 0, sizeof(d));
        len = 0;
    }
};
bign change(char s[]) {
    bign a;
    a.len = strlen(s);
    for (int i = 0; s[i]; i++) {
        a.d[i] = s[a.len - i - 1] - '0';
    }
    return a;
}
bign divide(bign a, int b, int &r) {
    bign c; c.len = a.len; //被除数的每一位和商的每一位一一对应, 因此先令长度相等
    for (int i = a.len - 1; i >= 0; i--) { //从高位开始
        r = r * 10 + a.d[i]; //和上一代的余数结合
        c.d[i] = r / b; //商
        r = r % b; //不改变a, 而是改变r
    }
    while (c.d[c.len - 1] == 0 && c.len > 1) c.len--;
    return c;
}

void print(bign a) {
    for (int i = a.len - 1; i >= 0; i--) {
        printf("%d", a.d[i]);
    }
}

int main() {
    char A[1010];
    int B, r = 0;
    scanf("%s%d", A, &B);
    bign a = change(A);
    print(divide(a, B, r)); //r以初值0传入
    printf(" %d", r);
    return 0;
}

B1018 锤子剪刀布 (20 分)

CodeUp做过这个题目。我的这个思路已经很好了,很直接,是比较容易理解的方法,通过switch-case在九种情况间跳转,记录甲乙赢的次数和平局的次数,就可以推断出甲乙输的次数。考虑到最后要输出字典序最小的解,干脆一开始就将三种手势按字典序排好,即BCJ,然后分别记录两人用这三种手势赢的次数,比较得到胜利次数最多的手势时,先设其为B,只有当C的次数真的大于B的时候,才更新B为C,同理将C更新为J。

#include <cstdio>
char c[] = {'B', 'C', 'J'};  //c[0] = 'B'...
/* 返回获胜次数最多的手势,如果解不唯一,则返回按字母序最小的手势字符 */
char checkWinWay(int m[])
{   // B C J
	int k = 0;
	for (int i = 1; i < 3; i++) 
		if (m[i] > m[k]) k = i;
	return c[k];
}
int main()
{
    int N, winJ, winY, par, J[3] = {0}, Y[3] = {0}; // B C J
    winJ = winY = par = 0;
    char a, b;
    scanf("%d", &N);
    
    for (int i = 0; i < N; i++) {
    	getchar();        // 吸收换行 
        scanf("%c %c", &a, &b);
        switch(a) {
            case 'C':
                switch(b) {
                    case 'C': par++; break;
                    case 'J': winJ++; J[1]++; break;
                    case 'B': winY++; Y[0]++; break;
                }
                break;
            case 'J':
                switch(b) {
                    case 'C': winY++; Y[1]++; break;
                    case 'J': par++; break;
                    case 'B': winJ++; J[2]++; break;
                }
                break;
            case 'B':
                switch(b) {
                    case 'C': winJ++; J[0]++; break;
                    case 'J': winY++; Y[2]++; break;
                    case 'B': par++; break;
                }
                break;
        }
    }
    printf("%d %d %d\n%d %d %d\n", winJ, par, N-winJ-par, winY, par, N-winY-par);
    printf("%c %c\n", checkWinWay(J), checkWinWay(Y)); 
    return 0;
}

还可以更加简化一下switch-case这一段,太长了。我们预先将三种手势按字典序排好后,发现这个顺序正好是循环相克顺序,即B胜C、C胜J、J胜B。因此可以提供一个映射函数将BCJ分别对应到0、1、2,对每组手势的比较先将其转换为数字再判断,由于设置的顺序恰好是循环相克顺序,因此c1胜c2的条件是(c1+1) % 3 == c2,平的条件是c1 == c2,输的条件(也是c2胜c1的条件)是(c2+1) % 3 == c1。这样就可以减少代码长度了。不过,switch-case的代码在时间复杂度上面可能会更优秀

#include <cstdio>
char c[] = {'B', 'C', 'J'};  //c[0] = 'B'...
char checkWinWay(int m[]);   //...略去, 代码见上
int change(char c) 
{   /* 提供手势字符到数字的转换 */
    return c == 'B' ? 0 : c == 'C' ? 1 : 2;
}

int main()
{   //winNote按顺序记录甲赢、乙赢、平局的次数; J和Y分别记录甲乙按B、C、J赢的次数
    int N, winNote[3] = {0}, J[3] = {0}, Y[3] = {0}; 
    char a, b;
    scanf("%d", &N); 
    
    for (int i = 0; i < N; i++) {
    	getchar();        //吸收上一句的换行 
        scanf("%c %c", &a, &b);
        int k1 = change(a), k2 = change(b);
        if ((k1 + 1) % 3 == k2) { //甲赢
            winNote[0]++; //甲赢次数+1
            J[k1]++;      //甲靠k1赢的次数+1
        } else if ((k2 + 1) % 3 == k1) { //乙赢
            winNote[1]++; //乙赢次数+1
            Y[k2]++;
        } else   //平局
            winNote[2]++;
    }
    printf("%d %d %d\n", winNote[0], winNote[2], N-winNote[0]-winNote[2]);
    printf("%d %d %d\n", winNote[1], winNote[2], N-winNote[1]-winNote[2]);
    printf("%c %c\n", checkWinWay(J), checkWinWay(Y)); 
    return 0;
}

B1019 数字黑洞 (20 分)

C++的sort函数真的是实用。本题的关键在于将数字转换成数组,排序得到递增和递减数组,并将之转化回数字,得到最小数和最大数。接下来就简单了。

#include <cstdio>
#include <algorithm>
using namespace std;
bool cmp(int a, int b) {
    return a > b; //递减排序函数
}

void toArray(int n, int nums[]) {
    for (int i = 0; i < 4; i++) {
        nums[i] = n % 10; //如果某步为3位数, 则视作高位补0
        n /= 10;  
    }
}
int toNumber(int nums[]) {
    int sum = 0;
    for (int i = 0; i < 4; i++) {
        sum = sum * 10 + nums[i];
    }
    return sum;
}
int main() {
    int N, min, max;
    scanf("%d", &N);
    int nums[4];
    while (1) {
        toArray(N, nums);     //将N转换为数组
        sort(nums, nums + 4); //递增排序
        min = toNumber(nums); //得到最小值
        sort(nums, nums + 4, cmp); //递减排序
        max = toNumber(nums); //得到最大值
        N = max - min;        //得到下一个数
        printf("%04d - %04d = %04d\n", max, min, N);
        //如果下一步是0, 说明该数全部位相同, 按题意输出后退出; 如果是6174则结束
        if (N == 0 || N == 6174) break; 
    }
    return 0;
}

B1020 月饼 (25 分)

先卖出单价最高的月饼,然后单价次之的月饼。

#include <cstdio>
#include <algorithm>
using namespace std;
struct mooncake {
    double price; //单价
    double sell;  //总售价
    double store; //库存量
} cake[1010];

bool cmp(struct mooncake a, struct mooncake b) {
    return a.price > b.price;  //按单价从高到低排序
}
int main() {
    int N;
    double D;
    scanf("%d%lf", &N, &D);
    for (int i = 0; i < N; i++) {
        scanf("%lf", &cake[i].store);
    }
    for (int i = 0; i < N; i++) {
        scanf("%lf", &cake[i].sell);
        cake[i].price = cake[i].sell / cake[i].store;  //计算单价 
    } 
    sort(cake, cake + N, cmp);
    double revenue = 0; //计算收益
    for (int i = 0; i < N; i++) {
        if (cake[i].store <= D) { //如果需求量高于月饼库存量
            D -= cake[i].store;
            revenue += cake[i].sell; //第一种月饼全部卖出
        } else {
            revenue += cake[i].price * D; //只卖出剩余需求量的月饼;
            break;
        }
    }
    printf("%.2f\n", revenue);
    return 0;
}

B1021 个位数统计 (15 分)

用字符数组的形式存储题目给定的正整数N,用直接映射的哈希表记录0-9数字出现的次数,打印次数不为0的那些数。

#include <cstdio>

int main() {
    char N[1020];
    int hashTable[10] = {0}; //0 1 2 3 4 5 6 7 8 9
    scanf("%s", N);
    for (int i = 0; N[i]; i++) hashTable[N[i] - '0']++;
    for (int i = 0; i < 10; i++) if (hashTable[i]) printf("%d:%d\n", i, hashTable[i]);

    return 0;
}

B1022 D进制的A+B (20 分)

#include <cstdio>

int main()
{
    int a, b, sum, d, nums[100], i = 0;
    scanf("%d%d%d", &a, &b, &d);
    sum = a + b;
    do {
        nums[i++] = sum % d;
        sum /= d;
    } while (sum != 0);
    for (int j = i - 1; j >= 0; j--) 
        printf("%d", nums[j]);
    return 0;
}

B1023 组个最小数 (20 分)

#include <cstdio>

int main() {
    int count[10]; //记录数字0-9个数
    for (int i = 0; i < 10; i++) 
        scanf("%d", &count[i]);
    for (int i = 1; i < 10; i++) { //从1-9中选择count不为0的最小的数字打印
        if (count[i]) {
            printf("%d", i);
            count[i]--;
            break;
        }
    }
    for (int i = 0; i < 10; i++) { //从0-9中输出对应个数的数字
        for (int j = 0; j < count[i]; j++) {
            printf("%d", i);
        }
    }
    return 0;
}

★★★ B1024 科学计数法 (20 分)

将一个符合科学记数法的字符串转换为浮点数的表示形式。

#include <cstdio>
#include <cstring>

int main() {
    char a[10100]; //实数A的存储长度不超过9999字节,且其指数的绝对值不超过9999
    scanf("%s", a); //输入测试用例
    /* 输出负号不输出正号 */
    if (a[0] == '-') printf("-");
    /* 第一步 找到E的位置*/
    int pos = 0;
    while (a[pos] != 'E') pos++;
    /* 第二步 求出右边指数的绝对值大小, 先不管符号 */
    int exp = 0;
    for (int i = pos + 2; a[i]; i++) {
        exp = exp * 10 + a[i] - '0';
    } //特判指数为0的情况
    if (exp == 0) for (int i = 1; i < pos; i++) printf("%c", a[i]);
    /* 第三步 根据指数正负分类输出 */
    else if (a[pos + 1] == '-') { //指数为负
        printf("0."); 
        for (int i = 0; i < exp - 1; i++) printf("0"); //输出普通数字表示法的小数点后面exp-1个连续的0
        /* 输出字母E之前的所有数字 */
        printf("%c", a[1]); //输出科学计数法小数点前面的1个数字
        for (int i = 3; i < pos; i++) printf("%c", a[i]); //输出除了小数点外的数字
    } else { //指数为正
        for (int i = 1; i < pos; i++) { //输出小数点移动后的数
            if (a[i] == '.') continue;  //掠过小数点
            printf("%c", a[i]);  //输出当前数位
            if (i == exp + 2 && pos - 3 != exp) printf(".");//在输出(exp + 2)位置上的数后面添加小数点
            //原小数点和E之间的数字个数(pos - 3)不能等于小数点右移位数expe, 不然就不用输出小数点了
        }
        for (int i = 0; i < exp - (pos - 3); i++) printf("0"); //如果指数够大, 输出多余的0 
    }
    return 0;
}

B1025 反转链表 (25 分)

给定一个常数K以及一个单链表L,请编写程序将L中每K个结点反转。例如:给定L为1→2→3→4→5→6,K为3,则输出应该为3→2→1→6→5→4;如果K为4,则输出应该为4→3→2→1→5→6,即最后不到K个元素不反转

  • 输入格式:
    每个输入包含1个测试用例。每个测试用例第1行给出第1个结点的地址、结点总个数正整数N(<= 10^5)、以及正整数K(<=N),即要求反转的子链结点的个数。结点的地址是5位非负整数,NULL地址用-1表示。
    接下来有N行,每行格式为:Address Data Next
    其中Address是结点地址,Data是该结点保存的整数数据,Next是下一结点的地址。
  • 输出格式:
    对每个测试用例,顺序输出反转后的链表,其上每个结点占一行,格式与输入相同。
  • 输入样例:
    00100 6 4
    00000 4 99999
    00100 1 12309
    68237 6 -1
    33218 3 00000
    99999 5 68237
    12309 2 33218
    
  • 输出样例:
    00000 4 33218
    33218 3 12309
    12309 2 00100
    00100 1 99999
    99999 5 68237
    68237 6 -1
    

分析:输入样例正确连接顺序应该是:

00100 1 12309
12309 2 33218
33218 3 00000
00000 4 99999
99999 5 68237
68237 6 -1

还应该考虑输入样例中有不在链表中的结点的情况。所以用个sum计数。而且,algorithm头文件里面有reverse函数可以直接调用

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005;
//静态链表
struct node {
    int data, next;
} List[maxn];

int main() {
    int first, n, k, temp;
    scanf("%d%d%d", &first, &n, &k);
    while (n--) {
        cin >> temp;
        cin >> List[temp].data >> List[temp].next;
    }
    int sum = 0, link[maxn]; //记录存在链表中的元素个数
    while (first != -1) {
        link[sum++] = first;
        first = List[first].next; //记录头指针和链接
    }
    for (int i = 0; i < sum - sum % k; i += k)  //剩下的小于k的不反转
        reverse(link + i, link + i + k);
    for (int i = 0; i < sum - 1; i++)  
        printf("%05d %d %05d\n", link[i], List[link[i]].data, link[i + 1]);
    printf("%05d %d -1", link[sum - 1], List[link[sum - 1]]); //打印最后一个结点
    return 0;
}

B1075 链表元素分类 (25 分)

给定一个单链表,请编写程序将链表元素进行分类排列,使得所有负值元素都排在非负值元素的前面,而[0, K]区间内的元素都排在大于K的元素前面。但每一类内部元素的顺序是不能改变的。例如:给定链表为 18→7→-4→0→5→-6→10→11→-2,K为10,则输出应该为 -4→-6→-2→7→0→5→10→18→11。

输入格式:

每个输入包含1个测试用例。每个测试用例第1行给出:第1个结点的地址;结点总个数,即正整数N (<= 105);以及正整数K (<=1000)。结点的地址是5位非负整数,NULL地址用-1表示。

接下来有N行,每行格式为:

Address Data Next

其中Address是结点地址;Data是该结点保存的数据,为[-105, 105]区间内的整数;Next是下一结点的地址。题目保证给出的链表不为空。

输出格式:

对每个测试用例,按链表从头到尾的顺序输出重排后的结果链表,其上每个结点占一行,格式与输入相同。

输入样例:
00100 9 10
23333 10 27777
00000 0 99999
00100 18 12309
68237 -6 23333
33218 -4 00000
48652 -2 -1
99999 5 68237
27777 11 48652
12309 7 33218
输出样例:
33218 -4 68237
68237 -6 48652
48652 -2 12309
12309 7 00000
00000 0 99999
99999 5 23333
23333 10 00100
00100 18 27777
27777 11 -1

分析:将结点用list[10000]保存,list为node类型,node中保存结点的值value和它的next地址。list的下标就是结点的地址。将<0、0~k、>k三部分的结点地址分别保存在v[0]、v[1]、v[2]中,最后将vector中的值依次输出即可~

#include <iostream>
#include <vector>
using namespace std;
struct node {
    int data, next;
}list[100000];
vector<int> v[3];
int main() {
    int start, n, k, a;
    scanf("%d%d%d", &start, &n, &k);
    for (int i = 0; i < n; i++) {
        scanf("%d", &a);
        scanf("%d%d", &list[a].data, &list[a].next);
    }
    int p = start;
    while(p != -1) {
        int data = list[p].data;
        if (data < 0)
            v[0].push_back(p);
        else if (data >= 0 && data <= k)
            v[1].push_back(p);
        else
            v[2].push_back(p);
        p = list[p].next;
    }
    int flag = 0;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < v[i].size(); j++) {
            if (flag == 0) {
                printf("%05d %d ", v[i][j], list[v[i][j]].data);
                flag = 1;
            } else {
                printf("%05d\n%05d %d ", v[i][j], v[i][j], list[v[i][j]].data);
            }
        }
    }
    printf("-1");
    return 0;
}

B1026 程序运行时间 (15 分)

本题和C语言中测试函数时间的方式很接近,可以触类旁通。同时本题对四舍五入的方法、时间(秒、分、时)的换算、输出格式都有一定的要求。

#include <cstdio>

int main()
{
    int c1, c2;
    scanf("%d%d", &c1, &c2);
    int ticks = (c2 - c1);  //按题目要求做差
    if (ticks % 100 >= 50) ticks = ticks / 100 + 1;  // 末两位如果>=50, 则除以100后必须+1
    else ticks /= 100;    //四舍五入
    printf("%02d:%02d:%02d", ticks / 3600, ticks % 3600 / 60, ticks % 3600 % 60);
    return 0; 
}

B1027 打印沙漏 (20分)

发现是道做过的题目。

#include <cstdio>

int main()
{
    int n, useSymbols = 1, beginSym = 1;
    char c;
    scanf("%d %c", &n, &c);
    /* beginSym用1个符号即沙漏中心, 要使用的符号数开始也是1, 然后逐层累计, 试出可以使用的最大符号数*/
    while (useSymbols <= n) {
        beginSym += 2;
        useSymbols += (beginSym * 2);
    }
    useSymbols -= (beginSym * 2); // 可使用的最大符号数
    beginSym -= 2; // 第一层和最后一层的符号数
    /* 开始打印沙漏 */
    for (int i = beginSym; i >= 1; i -= 2) {
        int spaceNum = beginSym - i;
        for (int k = 1; k <= spaceNum / 2; k++) 
            printf(" ");
        for (int k = 1; k <= i; k++)
            printf("%c", c);
        printf("\n");
    }  // 上半部分结束, 打印下半部分
    for (int i = 3; i <= beginSym; i += 2) {
        int spaceNum = beginSym - i;
        for (int k = 1; k <= spaceNum / 2; k++)
            printf(" ");
        for (int k = 1; k <= i; k++)
            printf("%c", c);
        printf("\n");
    }
    printf("%d\n", n - useSymbols);
    return 0;
}

另一种思路是我这次做的,即将沙漏的变化视作两个相同等差数列an,可使用的最大符号数为2 * Sn -1 <= n。等差数列求和公式如下。计算出一部分的层数,即可求出第一层的符号数和可使用的最大符号数。
在这里插入图片描述

#include <cstdio>
#include <cmath>
int main()
{
    int n, useSymbols = 1, beginSym = 1;
    char c;
    scanf("%d %c", &n, &c);
    // 视作一个等差数列和*2-1 <= n, 求出上半部分的层数
    int level = (int)sqrt((n + 1) / 2);
    beginSym = 1 + (level - 1) * 2;
    useSymbols = level * level * 2 - 1;
	...

★ B1028 人口普查 (20 分)

这个题目在逻辑上面和B1004一致,大体过程也一样。但是要注意几点:

  • 读入日期时可以使用%d/%d/%d的格式,这是我以前不太熟悉的;
  • 比较日期大小的函数,应该从年比较起,如果相同再比较月,再比较日
  • 这里由于输入的日期可能合法也可能不合法,甚至所有的都不合法,因此像B1004一样使用第一个输入的信息初始化oldest、youngest做不到,必须自己指定,oldest初始化为最晚的日期,youngest初始化为最早的日期。更新这两者的时候可能同时更新,因此需要使用if-if结构,不然会出错。
  • 如果所有的日期都不合法,必须特判输出0,不输出oldest和youngest。这一点也需要注意。
    总之,写这题给了我处理日期类题目的宝贵的经验。
#include <cstdio>
struct person {
    char name[10]; 
    int yy, mm, dd; //日期
} oldest, youngest, left, right, temp; //前两者存放最年长和年轻的人, 后两者存放合法日期的左右界限

//1814年9月6日: left和youngest; 2014年9月6日: right和oldest
void init() {
    youngest.yy = left.yy = 1814;
    oldest.yy = right.yy = 2014;
    youngest.mm = oldest.mm = left.mm = right.mm = 9;
    youngest.dd = oldest.dd = left.dd = right.dd = 6;
}
bool LessEqu(person a, person b) { //a的日期小于等于b, 返回true(a早于b)
    if (a.yy != b.yy) return a.yy <= b.yy;
    else if (a.mm != b.mm) return a.mm <= b.mm;
    else return a.dd <= b.dd;
}

bool MoreEqu(person a, person b) { //a的日期大于等于b, 返回true(a晚于b)
    if (a.yy != b.yy) return a.yy >= b.yy;
    else if (a.mm != b.mm) return a.mm >= b.mm;
    else return a.dd >= b.dd;
}

int main() {
    init();
    int n, legal_num = 0; //legal_num存放合法日期的人数
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%s %d/%d/%d", temp.name, &temp.yy, &temp.mm, &temp.dd);
        if (MoreEqu(temp, left) && LessEqu(temp, right)) {
            legal_num++;
            if (LessEqu(temp, oldest)) oldest = temp; //更新oldest
            if (MoreEqu(temp, youngest)) youngest = temp; //更新youngest
        }
    }
    if (legal_num == 0) printf("0\n");  //所有人的日期都不合法, 只输出0
    else printf("%d %s %s\n", legal_num, oldest.name, youngest.name);

    return 0;
}

B1029 旧键盘 (20 分)

坏掉的键等价于原字符串中出现而实际输入了的字符串中没有出现的字符旧键盘上坏了几个键,于是在敲一段文字的时候,对应的字符就不会出现。双指针分别指向原字符串和现字符串,可以用O(N)的时间解决问题。

#include <cstdio>
/* 整数/字符哈希题目 */
int main() {
    char old[100], now[100];
    scanf("%s%s", old, now);
    int Hash[150] = {0};
    for (int i = 0, j = 0; old[i] || now[j]; ) {
        if (old[i] == now[j]) { i++; j++; } 
        else if (old[i] != now[j]) {
            int t = (old[i] >= 'a' && old[i] <= 'z') ? old[i] - 'a' + 'A' : old[i];
            if (Hash[t] == 0) { printf("%c", t); Hash[t] = 1; }
            i++;
        }
    }
    printf("\n");
    return 0;
}

★★ B1030 完美数列 (25 分)

给定一个正整数数列,和正整数 p,设这个数列中的最大值是 M,最小值是 m,如果 M≤mp,则称这个数列是完美数列。现在给定参数 p 和一些正整数,请你从中选择尽可能多的数构成一个完美数列。

  • 输入格式:输入第一行给出两个正整数 N 和 p,其中 N(≤10​5​​)是输入的正整数的个数,p(≤10​9​​)是给定的参数。第二行给出N个正整数,每个数不超过10​9​​。
  • 输出格式:在一行中输出最多可以选择多少个数可以用它们组成一个完美数列。
  • 输入样例:
    10 8
    2 3 20 4 5 1 6 7 8 9
    
    • 输出样例:
    8
    

我自己提供一点测试样例,

  • 输入
    3 3
    1 3 3
    
  • 输出
    3
    

注意点:

  1. 数据范围要使用long long,因为p与序列中的元素都可能达到109
  2. 需要考虑非递减数列中有重复元素的情况,因此,应该获得非递减数列中第一个大于m * p的位置,以便选择更多的元素。
  3. 同样的,需要考虑数列中所有元素都小于m * p的情况,此时返回位置n。如果我们自己写二分查找的话。不过可以用upper_bound替代,更加简洁。
#include <cstdio>
#include <algorithm>
using namespace std;
/* 二分上下界为[left, right], left和right初始值覆盖解空间, 
传入的初值为[0, n]; 获得数列中第一个大于x的位置 */
int binarySearch(long long a[], int left, int right, long long x) {
	while (left < right) {
		int mid = (left + right) / 2;
		if (a[mid] > x) right = mid;
		else if (a[mid] <= x) left = mid + 1;
	}
	return left;
}
int main() {
    int n, p;
    scanf("%d%d", &n, &p);
    long long a[n];
    for (int i = 0; i < n; i++) scanf("%lld", &a[i]);
    sort(a, a + n);
    int ans = 0;
    for (int i = 0; i < n; i++) {
    	long long mp = a[i] * p; //p倍子数列最小值
        int j = binarySearch(a, i + 1, n, mp);   
        ans = j - i > ans ? j - i : ans; 
    }
    printf("%d\n", ans);
    return 0;
}

用upper_bound替代的版本:

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

int main() {
    int n, p;
    scanf("%d%d", &n, &p);
    long long a[n];
    for (int i = 0; i < n; i++) scanf("%lld", &a[i]);
    sort(a, a + n);
    long long ans = 0, mp; //p倍子数列最小值
    for (int i = 0; i < n; i++) {
    	mp = a[i] * p;
        long long *tail = upper_bound(a, a + n, mp); //找到连续递增子数列中mp的位置 
        ans = tail - (a + i) > ans ? tail - (a + i) : ans; 
    }
    printf("%lld\n", ans);
    return 0;
}

双指针的版本(O(N)):

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

int main() {
    int n, p;
    scanf("%d%d", &n, &p);
    long long a[n];
    for (int i = 0; i < n; i++) scanf("%lld", &a[i]);
    sort(a, a + n);
    int count = 0, i = 0, j = 0;
    while (i < n && j < n) { //j先不断右移, 直到恰好不满足条件, 过程中更新count
        while (j < n && a[j] <= (long long)a[i] * p) {
            count = max(count, j - i + 1); //更新计数器
            j++;
        }
        i++; //右移一位
    }
    printf("%d\n", count);
    return 0;
}

B1031 查验身份证 (15 分)

注意点:

  • 使用数组来存储校验码(用char,因为有个X)和权重的对应关系;
  • 只要前17位中存在非整数,就直接输出,而不应该继续计算校验和。
#include <cstdio>
char ZM[] = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'}; //从0开始
int weight[] = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}; //权重分配

int main() {
    int flag = 1, N;
    scanf("%d", &N);
    char id[20];
    while (N--) {
        scanf("%s", id);
        int sum = 0, f = 1; //对前17位数字加权求和
        for (int i = 0; i <= 16; i++) { //检查前17位是否全为数字
            if (id[i] > '9') { printf("%s\n", id); flag = f = 0; break; } 
            else sum += (id[i] - '0') * weight[i]; 
        }
        if (f) { //计算的和对11取模得到值Z
            if (ZM[sum % 11] != id[17]) { //最后1位校验码计算不准确 
                printf("%s\n", id); flag = 0;
            }
        }
    }
    if (flag) printf("All passed\n");
    return 0;
}

B1032 挖掘机技术哪家强 (20 分)

注意: 十万及以上级别的数组要在函数外面开

#include <cstdio>
int school[100010] = {0}; // 记录每个学校的总分, 初始化为0

int main()
{
    int N, schoolID, score;
    scanf("%d", &N);    
    for (int i = 0; i < N; i++) {
        scanf("%d%d", &schoolID, &score);
        school[schoolID] += score;  
    }
    int k = 1, max = school[1];
    for (int i = 2; i < N; i++) { // 从所有学校中选出总分最高的一个
        if (school[i] > max) {
            max = school[i];
            k = i;
        }
    }
    printf("%d %d\n", k, max);
    return 0;
}

B1033 旧键盘打字 (20 分)

#include <stdio.h>
char bad[130], input[100010];
int main() {
    gets(bad); gets(input);
    int hash[130] = {0}, i, j;
    for (i = 0; bad[i]; i++) {
        if (bad[i] != '+') {
            if (bad[i] >= 'A' && bad[i] <= 'Z') hash[bad[i] - 'A' + 'a'] = 1;
            hash[bad[i]] = 1;
        } 
        else if (bad[i] == '+') for (j = 'A'; j <= 'Z'; j++) hash[j] = 1;
    }
    for (i = 0; input[i]; i++) {
        if (hash[input[i]] == 0) printf("%c", input[i]); 
    }
    printf("\n");
    return 0;
}

★★★(分数类总结) B1034 有理数四则运算 (20 分)

注意:题目给的整数都在int范围,但是int*int就到达long long的最大范围了,这里要用long long才不会答案错误。

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
ll gcd(ll a, ll b) { //求a与b的最大公约数 
	return !b ? a : gcd(b, a % b); 
}

struct Fraction { //最简洁的写法, 假分数 
	ll up, down; //分子分母 
} a, b;

Fraction reduction(Fraction result) { //化简
	if (result.down < 0) { //分母为负数, 分子分母都变为相反数 
		result.up = -result.up;
		result.down = - result.down;
	}
	if (result.up == 0) { //如果分子为0 
		result.down = 1;  //分母为1 
	} else { //如果分子不为0, 约分 
		int d = gcd(abs(result.up), abs(result.down)); //分子分母的最大公约数 
		result.up /= d;  //约去最大公约数 
		result.down /= d; 
	} 
	return result; 
} 

Fraction add(Fraction f1, Fraction f2) { //f1+f2
	Fraction result;
	result.up = f1.up * f2.down + f2.up * f1.down; //分数和的分子
	result.down = f1.down * f2.down; //分数和的分母
	return reduction(result); 
}

Fraction minu(Fraction f1, Fraction f2) { //f1+f2
	Fraction result;
	result.up = f1.up * f2.down - f2.up * f1.down; //分数差的分子
	result.down = f1.down * f2.down; //分数差的分母
	return reduction(result);
}

Fraction multi(Fraction f1, Fraction f2) { //f1*f2
	Fraction result;
	result.up = f1.up * f2.up; //分数乘的分子
	result.down = f1.down * f2.down; //分数乘的分母
	return reduction(result); 
}

Fraction divide(Fraction f1, Fraction f2) { //f1/f2
	Fraction result;
	result.up = f1.up * f2.down; //分数除的分子
	result.down = f1.down * f2.up; //分数除的分母
	return reduction(result);
}

void showResult(Fraction r) {
	r = reduction(r);
	if (r.up < 0) printf("(");     //为负数则须加括号
	if (r.down == 1) printf("%lld", r.up); //整数
	else if (abs(r.up) > r.down) { //假分数 
		printf("%lld %lld/%lld", r.up / r.down, abs(r.up) % r.down, r.down); 
	} else {  //真分数 
		printf("%lld/%lld", r.up, r.down);
	}
	if (r.up < 0) printf(")");
}  

int main() {
	scanf("%lld/%lld %lld/%lld", &a.up, &a.down, &b.up, &b.down);
	//加法
	showResult(a); printf(" + "); showResult(b); printf(" = ");
	showResult(add(a, b)); printf("\n");
	//减法
	showResult(a); printf(" - "); showResult(b); printf(" = ");
	showResult(minu(a, b)); printf("\n");
	//乘法 
	showResult(a); printf(" * "); showResult(b); printf(" = ");
	showResult(multi(a, b)); printf("\n");
	//除法
	showResult(a); printf(" / "); showResult(b); printf(" = ");
	if (b.up == 0) printf("Inf");  //若除法分母为0,则输出Inf
	else showResult(divide(a, b)); 
	printf("\n");
	return 0;
}

★★★ B1035 插入与归并 (25 分)

有陷阱,中间序列可能和初始序列一样,但是它们不是同一轮迭代的序列,初始序列不应该与中间序列比较,不然有一个数据会产生双解。

//input
4
3 4 2 1
3 4 2 1
//output
Insertion Sort
2 3 4 1

这里的中间序列是插入排序在经过第一轮迭代后的结果,虽然和目标序列一样,因此要输出第二轮插入后的结果。
另外,由于数据范围较小,归并排序可以不用合并函数而用sort替代

#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 111;
int origin[N], tempOri[N], target[N]; //原始数组 原始数组备份 目标数组
int n; //元素个数
bool isSame(int a[], int b[]) {
    for (int i = 0; i < n; i++) {
        if (a[i] != b[i]) return false;
    }
    return true;
}
void traceArray(int a[]) { //输出数组
    for (int i = 0; i < n; i++) {
        printf("%d", a[i]);
        if (i < n - 1) printf(" ");
    }
    printf("\n");
}

bool insertSort() { //插入排序
    bool f = false; //记录是否在数组的中间步骤与目标数组相同
    for (int i = 1; i < n; i++) {
        if (i != 1 && isSame(tempOri, target)) {
            f = true; //中间步骤与目标相同, 且初始序列不参与与目标数组的比较
        }
        int t = tempOri[i], j;
        for (j = i; j > 0 && tempOri[j - 1] > t; j--) 
            tempOri[j] = tempOri[j - 1];
        tempOri[j] = t;
        if (f) { //已到达目标数组的下一次排序
            return true;
        }
    }
    return false; //无法到达目标数组
}

void mergeSort() { //归并排序
    bool f = false; 
    for (int step = 2; step / 2 <= n; step *= 2) {
        if (step != 2 && isSame(tempOri, target))  {
            f = true; //中间步骤与目标相同, 且初始序列不参与与目标数组的比较
        }
        for (int i = 0; i < n; i += step) {
            sort(tempOri + i, tempOri + min(i + step, n));
        }
        if (f) { //已到达目标数组的下一次排序
            traceArray(tempOri);
            return;
        }
    }
    return; //无法到达目标数组
}
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &origin[i]); //输入初识序列
        tempOri[i] = origin[i]; //备份, 排序在tempOri上面进行
    }
    for (int i = 0; i < n; i++) {
        scanf("%d", &target[i]); //目标数组
    }
    if (insertSort()) { //如果插入排序中找到目标数组
        printf("Insertion Sort\n");
        traceArray(tempOri); 
    } else { //到达此处时一定时归并排序
        printf("Merge Sort\n");
        for (int i = 0; i < n; i++) {
            tempOri[i] = origin[i]; //还原备份数组
        }
        mergeSort(); //归并排序
    }
}

B1036 跟奥巴马一起编程 (15)

这类画图题目的做法通常有两类

  • 通过规律直接输出;
  • 定义一个二维字符数组,通过规律填充之,然后输出这个二维数组。
#include <cstdio>

int main()
{
    int col;
    char c;
    scanf("%d %c", &col, &c);
    //四舍五入 由于除以2, 通过判断奇偶数以避免浮点舍入
    int row = col % 2 ? col / 2 + 1 : col / 2;
    //第1行
    for (int i = 0; i < col; i++)
        printf("%c", c);
    printf("\n");
    //第2至row-1行
    for (int i = 2; i < row; i++) {
        printf("%c", c);
        for (int j = 0; j < col - 2; j++) 
            printf(" ");
        printf("%c\n", c);
    }
    //第row行
    for (int i = 0; i < col; i++)
        printf("%c", c);
    printf("\n");
}

B1037 在霍格沃茨找零钱 (20 分)

这种题目和时分秒转换的方法一样,但和日期转换的方法不太一样,毕竟日期有平年闰年大月小月。

#include <cstdio>

int main() {
    int galleon = 17 * 29, sickle = 29;
    int g1, s1, k1, g2, s2, k2;
    scanf("%d.%d.%d %d.%d.%d", &g1, &s1, &k1, &g2, &s2, &k2);
    int price = g1 * galleon + s1 * sickle + k1, money = g2 * galleon + s2 * sickle + k2;
    int change = money - price;
    if (change < 0) {
        printf("-");
        change = -change;
    }
    printf("%d.%d.%d\n", change / galleon, change % galleon / sickle, change % sickle); 
    return 0;
}

B1038 统计同成绩学生 (20 分)

#include <cstdio>
int hashTable[105] = {0};
int main() {
    int N, t, K;
    scanf("%d", &N);
    for (int i = 0; i < N; i++) {
        scanf("%d", &t); 
        hashTable[t]++;
    }
    scanf("%d", &K);
    for (int i = 0; i < K; i++) {
        scanf("%d", &t);
        if (i > 0) printf(" ");
        printf("%d", hashTable[t]);
    }
    printf("\n");
    return 0;
}

B1039 到底买不买 (20 分)

奇了怪了,为甚麽这里写成else if (neg) printf("Yes %d\n", -neg);就不行,就有一条通不过呢?而写成else就可以通过?

#include <cstdio>
int main() {
    char buy[1010], want[1010];
    int hash[130] = {0};
    scanf("%s", buy); scanf("%s", want);
    for (int i = 0; want[i]; i++) hash[want[i]]++;
    for (int i = 0; buy[i]; i++) hash[buy[i]]--;
    int pos = 0, neg = 0; //如果哈希表全部<=0, Yes, 负数之和即多的珠子
    for (int i = 0; i < 128; i++) {
        if (hash[i] > 0) pos += hash[i];
        else if (hash[i] < 0) neg += hash[i];
    }
    if (pos) printf("No %d\n", pos); //只要存在pos, 说明缺少想要的珠子
    else printf("Yes %d\n", -neg); //strlen(buy) - strlen(want)
    return 0;
}

★★ B1040 有几个PAT (25 分)

字符串APPAPT中包含了两个单词PAT,其中第一个PAT是第2位§,第4位(A),第6位(T);第二个PAT是第3位§,第4位(A),第6位(T)。现给定字符串,问一共可以形成多少个PAT

  • 输入格式:输入只有一行,包含一个字符串,长度不超过10​5​​,只包含 P、A、T 三种字母。
  • 输出格式:在一行中输出给定字符串中包含多少个 PAT。由于结果可能比较大,只输出对 1000000007 取余数的结果。
  • 输入样例:
    APPAPT  APPAPTT
    
  • 输出样例:
    2   4
    

对一类涉及序列的题目来说可以活用递推关系,如果序列的每一位所需要的值/进行的判断,都可以通过该位左右两侧的结果计算得到,就可以考虑所谓“左右两侧的结果”是否可以通过递推进行预处理得到。这也是一种用空间换时间的方式之一
另外,每次计算记得取模。

#include <cstdio>
char s[100010];
int leftPNum[100010] = {0}, rightTNum = 0; //每一位左位含P的个数; 每一位右位含T的个数 

int main() {
    scanf("%s", s); 
    int i, j;
    leftPNum[0] = s[0] == 'P' ? 1 : 0; 
    for (i = 1; s[i]; i++) {
    	if (s[i] == 'P') leftPNum[i] = leftPNum[i - 1] + 1;
    	else leftPNum[i] = leftPNum[i - 1];
	}
	int ans = 0; //答案 
	for (j = i - 1; j >= 0; j--) {
		if (s[j] == 'T') rightTNum++; //当前位是T, 右边T的个数加一 
		else if (s[j] == 'A') ans = (ans + leftPNum[j] * rightTNum) % 1000000007;;
	}
    printf("%d\n", ans);
    return 0;
}

B1041 考试座位号 (15 分)

准考证号由14位数字组成,因此可以使用long long来存放它
这里直接把试机座位号作为数组的下标,可以直接通过试机座位号来获取考生的准考证号和考试座位号,在使用记录少、空间足够的情况下,时间效率相当高。这就是哈希表的雏形了。

#include <cstdio>
typedef struct student {
    long long id; //准考证号
    int examSeat; //考试座位号
} student;
student stu[1010];

int main()
{
    int N, M;
    scanf("%d", &N);    //考生人数
    long long id; 
    int seat, examSeat; 
    for (int i = 0; i < N; i++) {
        scanf("%lld %d %d", &id, &seat, &examSeat); //准考证号 试机座位号 考试座位号
        stu[seat].id = id;  //试机座位号为seat的考生的准考证号
        stu[seat].examSeat = examSeat; //试机座位号为seat的考生的考试座位号
    }
    scanf("%d", &M);   //查询个数
    for (int i = 0; i < M; i++) {
        scanf("%d", &seat);
        printf("%lld %d\n", stu[seat].id, stu[seat].examSeat);
    }
    return 0;
}

B1042 字符统计 (20 分)

只统计英文字符,不区分大小写,空格、’&'这些字符不应该被统计。

#include <cstdio>
int hash[30] = {0}, max = 0;
int change(char c) {
    if (c >= 'A' && c <= 'Z') return c - 'A';
    if (c >= 'a' && c <= 'z') return c - 'a';
}
int main() {
    char s[1000];
    while (scanf("%s", s) != EOF) {
        for (int i = 0; s[i]; i++) {
            if ((s[i] >= 'A' && s[i] <= 'Z') || (s[i] >= 'a' && s[i] <= 'z')) {
                int id = change(s[i]);
                hash[id]++;
            }
        }
    }
    for (int i = 0; i < 26; i++) if (hash[i] > hash[max]) max = i;
    printf("%c %d\n", max + 'a', hash[max]);
    return 0;
}

为了避免缓冲区溢出,从终端读取输入时应当用fgets()代替gets()函数。但是这也将带来一个问题,因为fgets()的调用格式是:

fgets (buf, MAX, fp)
fgets (buf, MAX, stdin)

buf是一个char数组的名称,MAX是字符串的最大长度,fp是FILE指针。
fgets()函数读取到它所遇到的第一个换行符的后面,或者读取比字符串的最大长度少一个的字符,或者读取到文件结尾。然后fgets()函数向末尾添加一个空字符以构成一个字符串。
如果在达到字符最大数目之前读完一行,它将在字符串的‘\0’字符之前添加一个换行符。问题出在有时字符串的结尾处可能多出一个换行符,我们可能需要把它去掉

#include <cstdio>
int hash[30] = {0}, max = 0;
int change(char c) {
    if (c >= 'A' && c <= 'Z') return c - 'A';
    if (c >= 'a' && c <= 'z') return c - 'a';
}
int main() {
    char s[1010];
    fgets(s, 1010, stdin);
    for (int i = 0; s[i]; i++) {
        if ((s[i] >= 'A' && s[i] <= 'Z') || (s[i] >= 'a' && s[i] <= 'z')) {
            int id = change(s[i]);
            hash[id]++;
        }
    }
    for (int i = 0; i < 26; i++) if (hash[i] > hash[max]) max = i;
    printf("%c %d\n", max + 'a', hash[max]);
    return 0;
}

B1043 输出PATest (20 分)

并非直接哈希,而是限制了一个范围(0-5),手动加上了一个映射和字典。

#include <cstdio>
char s[10010];
int hash[6] = {0}, sum = 0; //记录PATest这六个字符的个数; 需要输出的总字符数 
char dict[] = {'P', 'A', 'T', 'e', 's', 't'}; //字典, 输出时用到
int change(char c) {
    int t = -1;
    switch (c) {
        case 'P': t = 0; break;
        case 'A': t = 1; break;
        case 'T': t = 2; break;
        case 'e': t = 3; break;
        case 's': t = 4; break;
        case 't': t = 5; break;
    }
    return t;
}
int main() {
    fgets(s, 10010, stdin);
    for (int i = 0; s[i]; i++) {
        int t = change(s[i]);
        if (t != -1) { hash[t]++; sum++; }
    }
    while (sum) {
        for (int i = 0; i < 6; i++) {
            if (hash[i] > 0) {
                printf("%c", dict[i]);
                hash[i]--; sum--;
            }
        }
    }
    return 0;
}

★★ B1044 火星数字 (20 分)

火星人是以 13 进制计数的:

 地球人的 0 被火星人称为 tret。
 地球人数字 1 到 12 的火星文分别为:jan, feb, mar, apr, may, jun,
 jly, aug, sep, oct, nov, dec。
 火星人将进位以后的 12 个高位数字分别称为:tam, hel, maa, huh,
 tou, kes, hei, elo, syy, lok, mer, jou。

例如地球人的数字 29 翻译成火星文就是 hel mar;而火星文 elo nov 对应地球数字 115。为了方便交流,请你编写程序实现地球和火星数字之间的互译。

  • 输入格式:输入第一行给出一个正整数 N(<100),随后 N 行,每行给出一个 [0, 169) 区间内的数字 —— 或者是地球文,或者是火星文。
  • 输出格式:对应输入的每一行,在一行中输出翻译后的另一种语言的数字。
  • 输入样例:
    4
    29
    5
    elo nov
    tam
    
  • 输出样例:
    hel mar
    may
    115
    13
    

如果对输入进行模拟会相对复杂,考虑到数据范围小,不如先把所有可能的结果得到,再直接查询。需要注意的是:13的倍数不应该输出个位的"tret",而是应该直接输出高位数字。

#include <cstdio>
#include <iostream>
#include <string>
#include <map>
using namespace std;

string unitDigit[13] = {"tret", "jan", "feb", "mar", "apr", "may", "jun", "jly", "aug", "sep", "oct", "nov", "dec"};
string tenDigit[13] = {"tret", "tam", "hel", "maa", "huh", "tou", "kes", "hei", "elo", "syy", "lok", "mer", "jou"};
map<string, int> strToNum; //火星文转数字
string numTostr[170]; //数字转火星文

void init() {
    for (int i = 0; i < 13; i++) {
        numTostr[i] = unitDigit[i]; //个位为[0, 12], 十位为0
        strToNum[unitDigit[i]] = i;
        numTostr[13 * i] = tenDigit[i]; //十位为[0, 12], 个位为0
        strToNum[tenDigit[i]] = i * 13;
    }
    for (int i = 1; i < 13; i++) { //十位
        for (int j = 1; j < 13; j++) { //个位
            string str = tenDigit[i] + " " + unitDigit[j]; //火星文
            numTostr[i * 13 + j] = str; //数字转火星文
            strToNum[str] = i * 13 + j; //火星文转为数字
        }
    }
}

int main() {
    init(); //打表
    int T; scanf("%d", &T); //查询个数 
    getchar();
    while (T--) {
        string str;
        getline(cin, str); //查询的数
        if (str[0] >= '0' && str[0] <= '9') { //如果是数字
            int num = 0; //字符串转为数字
            for (int i = 0; i < str.length(); i++) {
                num = num * 10 + (str[i] - '0');
            }
            cout << numTostr[num] << endl; //直接查表
        } else { //如果是火星文
            cout << strToNum[str] << endl; //直接查表
        }
    }
    return 0;
}

★★ B1045 快速排序 (25 分)

著名的快速排序算法里有一个经典的划分过程:我们通常采用某种方法取一个元素作为主元,通过交换,把比主元小的元素放到它的左边,比主元大的元素放到它的右边。 给定划分后的 N 个互不相同的正整数的排列,请问有多少个元素可能是划分前选取的主元?

例如给定 N = 5 N = 5 N=5, 排列是1、3、2、4、5。则:

 1 的左边没有元素,右边的元素都比它大,所以它可能是主元;
 尽管 3 的左边元素都比它小,但其右边的 2 比它小,所以它不能是主元;
 尽管 2 的右边元素都比它大,但其左边的 3 比它大,所以它不能是主元;
 类似原因,4 和 5 都可能是主元。

因此,有3个元素可能是主元。

  • 输入格式:输入在第 1 行中给出一个正整数 N(≤10​5​​); 第 2 行是空格分隔的 N 个不同的正整数,每个数不超过 10​9​​。
  • 输出格式:在第 1 行中输出有可能是主元的元素个数;在第 2 行中按递增顺序输出这些元素,其间以 1 个空格分隔,行首尾不得有多余空格。
  • 输入样例:
    5
    1 3 2 4 5
    
  • 输出样例:
    3
    1 4 5
    

部分正确 18,采用双指针,测试点1、3、5运行超时

#include <cstdio>

int main() {
	int n;
	scanf("%d", &n);
	int a[n], pivot[n], pNum = 0;
	for (int i = 0; i < n; i++) scanf("%d", &a[i]);
	for (int i = 0; i < n; i++) {
		int low = 0, high = n - 1;
		if (i == 0) {
			while (a[high] > a[low]) high--; 
		} else if (i == n - 1) {
			while (a[low] < a[high]) low++;
		} else {
			while (a[low] < a[i]) low++;
			while (a[high] > a[i]) high--;
		}
		if (high == low) pivot[pNum++] = a[low]; //相等时为主元
	}	
	printf("%d\n", pNum);
	for (int i = 0; i < pNum; i++) {
		if (i > 0) printf(" ");
		printf("%d", pivot[i]);
	}
	printf("\n");
	return 0;
}

看了下题解,本题和B1040/A1093/A1101很像,思维类似,而和快排没有任何关系。

考虑大小的继承关系,序列为A,元素各不相同但都是正整数。令数组leftMax[i]记录A[i]左边的最大值(从A[0]-A[i-1],不包括A[i]本身);令数rightMin[i]记录A[i]右边的最小值(从A[i+1]-A[n-1],不包括A[i]本身),那么,如果A[i]大于左边的最大值leftMax[i]并且A[i]小于右边的最小值rightMin[i],说明左边的所有数都比它大,右边的所有数都比他小,A[i]为主元。

可以用O(n)的时间求出这两个数组,方法很简单:leftMax[i-1]记录了从A[0]到A[i-2]的最大值,leftMax[i] = A[i-1] > leftMax[i-1] ? A[i-1] : leftMax[i-1];同理可求出rightMin[i]。

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

const int maxn = 100010;
const int INF = 0x3fffffff; //10^10左右, 一个很大的数
int a[maxn], leftMax[maxn], rightMin[maxn]; //大于左边最大, 小于右边最小, 即为主元

int main() {
	int n;
	scanf("%d", &n);
	int pivot[n], pNum = 0; //记录主元和个数
	for (int i = 0; i < n; i++) scanf("%d", &a[i]);

    leftMax[0] = 0; //a[0]的左边没有更大的数
	for (int i = 1; i < n; i++) leftMax[i] = max(leftMax[i - 1], a[i - 1]);
    rightMin[n - 1] = INF; //a[n-1]的右边没有更小的数
	for (int i = n - 2; i >= 0; i--) rightMin[i] = min(rightMin[i + 1], a[i + 1]);

	for (int i = 0; i < n; i++) //左边所有数比他小, 右边所有数比他大
        if (a[i] > leftMax[i] && a[i] < rightMin[i]) pivot[pNum++] = a[i];

	printf("%d\n", pNum);
	for (int i = 0; i < pNum; i++) {
		if (i > 0) printf(" ");
		printf("%d", pivot[i]);
	}
	printf("\n"); //必须要有换行, 即使第二行没有输出主元, 个数为0
	return 0;
}

B1046 划拳 (15)

#include <cstdio>

int main()
{
    int N, failJ = 0, failY = 0; //甲乙输的次数
    scanf("%d", &N);
    while (N--) {
        int j1, j2, y1, y2;
        scanf("%d%d%d%d", &j1, &j2, &y1, &y2);
        if (j2 == j1 + y1 && y2 != j1 + y1) failY++;  //甲猜中乙没有
        else if (y2 == j1 + y1 && j2 != j1 + y1) failJ++; //乙猜中甲没有
    }
    printf("%d %d\n", failJ, failY);
    return 0;
}

B1047 编程团体赛 (20 分)

要统计每个队伍的总分,因此直接开一个哈希数组,来记录对应的各个队伍队员分数之和。

#include <cstdio>
int hash[1001] = {0};
int main() {
    int N;
    scanf("%d", &N);
    int c, p, g; //队伍编号-队员编号 成绩
    while (N--) {
        scanf("%d-%d %d", &c, &p, &g);
        hash[c] += g;
    }
    int max = 1;
    for (int i = 1; i < 1001; i++) if (hash[i] > hash[max]) max = i;
    printf("%d %d\n", max, hash[max]);
    return 0;
}

B1048 数字加密 (20 分)

  • 这里采用字符数组a,b记录两个正整数,然后定义字符串c记录结果;
  • 由于要从个位(低位)开始,因此使用reverse翻转a,b来让两个整数对齐,<algorithm>的reverse和swap函数都挺好用的;
  • 由于字符串下标从0开始,要将题目中的奇数位和偶数位的处理反过来;
  • 别忘了对字符数组操作后在最后一位补上’\0’,或者直接声明字符数组为char s = {0}
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
char JQK[] = {'J', 'Q', 'K'};
int main() {
    char a[110], b[110], c[200];
    scanf("%s%s", a, b);
    int len1 = strlen(a), len2 = strlen(b), size = 0;
    reverse(a, a + len1);
    reverse(b, b + len2);
    for (int i = 0; i < len1 || i < len2; i++) {
        int d1 = i < len1 ? a[i] - '0' : 0, d2 = i < len2 ? b[i] - '0' : 0;
        if (i % 2 == 0) {
            int temp = (d1 + d2) % 13;
            c[size++] =  temp > 9 ? JQK[temp - 10] : temp + '0';
        } else {
            int t = (d2 - d1);
            c[size++] = t >= 0 ? t + '0' : t + 10 + '0';
        }
    }
    c[size] = '\0';
    reverse(c, c + size);
    printf("%s\n", c);
    return 0;
}

B1049 数列的片段和 (20 分)

多试几个例子找规律,第i位的出现次数为i*(n+1-i)

#include <cstdio>

int main() {
    int n;
    double v, ans = 0;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%lf", &v);
        ans += v * i * (n + 1 - i); //第i位的出现次数为i*(n+1-i)
    }
    printf("%.2lf\n", ans);
    return 0;
}

★★ B1050 螺旋矩阵 (25 分)

本题要求将给定的 N 个正整数按非递增的顺序,填入“螺旋矩阵”。所谓“螺旋矩阵”,是指从左上角第1个格子开始,按顺时针螺旋方向填充。要求矩阵的规模为 m 行 n 列,满足条件:m×n 等于 N;m≥n;且 m−n 取所有可能值中的最小值。

  • 输入格式:
    输入在第 1 行中给出一个正整数 N,第 2 行给出 N 个待填充的正整数。所有数字不超过 10​4​​,相邻数字以空格分隔。
  • 输出格式:
    输出螺旋矩阵。每行 n 个数字,共 m 行。相邻数字以 1 个空格分隔,行末不得有多余空格。
  • 输入样例:
    12
    37 76 20 98 76 42 53 95 60 81 58 93
    
  • 输出样例:
    98 95 93
    42 37 81
    53 20 76
    58 60 76
    

和蛇形填数一模一样的。但是查了好久bug,就是没想明白问题出在哪里,后来发现,使用memset置0而非依赖初始化时={0},程序才能正常运行,真是奇怪。

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
bool cmp(int a, int b) {
    return a > b;
}
int main() {
    int N, m, n; //m行n列, m>=n; m*n=N; m-n最小
    scanf("%d", &N);
    n = sqrt(N); //n <=sqrt(N) <=m
    while (N % n) { n--; } //N可能为素数 
	m = N / n;
    int a[N], matrix[m][n]; //待填充的都是正整数, 用0作为没有填数的标识
    for (int i = 0; i < N; i++) scanf("%d", &a[i]);
	sort(a, a + N, cmp); //非递增排序 
	memset(matrix, 0, sizeof(matrix));
    
	int x = 0, y = 0, tot = 0;
    matrix[x][y] = a[tot++]; 
	while (tot < N) { //填完所有的符号为止
        while (y + 1 < n && !matrix[x][y + 1]) matrix[x][++y] = a[tot++]; //先往填右
        while (x + 1 < m && !matrix[x + 1][y]) matrix[++x][y] = a[tot++]; //再往下填
        while (y - 1 >= 0 && !matrix[x][y - 1]) matrix[x][--y] = a[tot++]; //再往左填
        while (x - 1 >= 0 && !matrix[x - 1][y]) matrix[--x][y] = a[tot++]; //再往上填
    }
	for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
			if (j > 0) printf(" ");
            printf("%d", matrix[i][j]);
        }
        printf("\n");
    }
    return 0;
}

另一种实现:

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
int N;
void solve(){
    int a[N];
    for(int i = 0; i < N; i++){
        scanf("%d", &a[i]);
    }
    sort(a, a + N);
    int n = sqrt(N), m;
    while(N % n){
        n--;
    }
    m = N / n;
    int t[m][n];
    for(int side = 0, k = N-1; side * 2 < n; side++){
        for(int j = side; j < n-side; j++){
            t[side][j] = a[k--];
        }
        for(int i = side + 1; i < m-side;i++){
            t[i][n-1-side] = a[k--];
        }
        for(int j = n-2-side;j >= side; j--){
            t[m-1-side][j] = a[k--];
        }
        if(n-1-side > side){
            for(int i = m-2-side; i >= side + 1; i--){
                t[i][side] = a[k--];
            }
        }
    }
    for(int i = 0; i < m; i++){
        for(int j = 0; j < n; j++){
            printf("%d", t[i][j]);
            if(j + 1< n){
                printf(" ");
            }
        }
        printf("\n");
    }
}
int main(){
    scanf("%d", &N);
    solve();
    return 0;
}

★ B1051 复数乘法 (15 分)

这里涉及到浮点数的比较问题,有点麻烦。

  1. 先按照题目描述,把极坐标形式等价到直角坐标形式;
  2. 题目要求实部和虚部都保留两位小数,因此fabs(real)这两句话是不可少的,否则会有-0.00的出现。e.g: -0.001,保留两位小数,变成-0.00,这就会导致有两个案例wrong answer。
  3. 在比较虚部是否小于0的时候,不要粗心写成(int)imag < 0,这会导致-0.1这类的虚部输出错误,可以直接写成imag < 0(0隐式转为浮点数)或者imag < 0.0。这在简单的比较情况下是可以忽略误差的,但是最好的做法还是如下所示。
#include <cstdio>
#include <cmath>
const double eps = 1e-8;
#define Less(a, b) (((a) - (b)) < (-eps))
int main() {
    double r1, p1, r2, p2;
    scanf("%lf%lf%lf%lf", &r1, &p1, &r2, &p2);
    double b = r1 * r2;
    double real = b * (cos(p1) * cos(p2) - sin(p1) * sin(p2));
	double imag = b * (cos(p1) * sin(p2) + sin(p1) * cos(p2));
	
	if (fabs(real) < 0.005) real = 0;
    if (fabs(imag) < 0.005) imag = 0;
    
    if (Less(imag, 0)) printf("%.2lf%.2lfi\n", real, imag);
    else printf("%.2lf+%.2lfi\n", real, imag);
    return 0;
}

B1052 卖个萌 (20 分)

注意:

  1. “Are you kidding me? @\/@”的’\’是转义字符,想要输出’\’就要用’\\’表示;
  2. 第一个测试点里面包含了空格,所以用cin会失败的,要用getline才能读入一行字符串。虽然总结了五种输入带空格的一行的方法,但是如非必要还是使用字符串string;
  3. 用户选择的序号不一定合法,比如说超过了最大值或者小于1,没注意到小于1测试点2会错误。
  4. 这里的emoji是一个map数组,每一个map代表了手、眼、口的可选符号集及其序号,每个符号可能有1-4个字符,因此要使用string的连接特性+=,不过更简单的是使用substr函数。
  5. 我对C++的使用没有对C那么熟练,因此C++的代码往往混杂了大量的C写法,从以往手动操作字符数组可见一斑,不过PAT甲乙级并非所有题目都严格要求时间效率,而且好的算法才是时间优化的大头,还需要多加熟练stl的操作。
#include <cstdio>
#include <string>
#include <map>
#include <iostream>
using namespace std;

int main() {
	map<int, string> emoji[3]; //手、眼、口的可选符号集及其序号 
	for (int c = 0; c < 3; c++) {
		string use; 
		getline(cin, use);
		int k = 1;
		for (int i = 0; i < use.length(); ) {
			string t1 = "";
			if (use[i] == '[') {
				while (use[++i] != ']') t1 += use[i];
				//cout << t1 << endl;
				emoji[c][k++] = t1; //符号序号与符号映射 
				i++; //移到下一个[] 
			} else i++;
		}
	}
	
	int n, t[5], index; scanf("%d", &n);
	while (n--) {
		int flag = 1;
		for (int i = 0; i < 5; i++) {
			scanf("%d", &t[i]);
			index = i > 2 ? 5 - i - 1 : i;
			if (t[i] > emoji[index].size() || t[i] < 1) {
				flag = 0;
			}
		}
		if (flag) {
		    for (int i = 0; i < 5; i++) {
				index = i > 2 ? 5 - i - 1 : i;
				cout << emoji[index][t[i]]; 
				if (i == 0) cout << "(";      //打印左脸颊 
				else if (i == 3) cout << ")"; //打印右脸颊 
			}
	        cout << endl;	
		} else {
			printf("Are you kidding me? @\\/@\n");
		}
	}
	return 0;
}

B1053 住房空置率 (20 分)

输出%要转义为%%。

#include <cstdio>
const double eps = 1e-8;
#define Less(a, b) (((a) - (b)) < -eps)
int main() {
    int n, d; //居民区住房总套数 观察期阈值
    double e; //低电量阈值
    scanf("%d%lf%d", &n, &e, &d);
    int probEmpty = 0, empty = 0; //可能空置户数 空置户数
    for (int i = 0; i < n; i++) {
        int k, lowEday = 0; //低于某给定的阈值e天数 
        double t;
        scanf("%d", &k);
        for (int j = 0; j < k; j++) {
            scanf("%lf", &t);
            if (Less(t, e)) lowEday++;
        }
        if (2 * lowEday > k) {  //超过一半的日子用电量低于某给定的阈值e
            if (k > d) empty++; //超过某给定阈值D天数, 为空置
            else probEmpty++;
        }
    }
    printf("%.1lf%% %.1lf%%\n", probEmpty * 100 / (double)n, empty * 100 / (double)n);
    return 0;
}

★(sscanf/sprintf) B1054 求平均值 (20 分)

本题的基本要求非常简单:给定 N 个实数,计算它们的平均值。但复杂的是有些输入数据可能是非法的。一个“合法”的输入是 [−1000,1000] 区间内的实数,并且最多精确到小数点后 2 位。当你计算平均值的时候,不能把那些非法的数据算在内。

  • 输入格式:
    输入第一行给出正整数 N(≤100)。随后一行给出 N 个实数,数字间以一个空格分隔。
  • 输出格式:
    对每个非法输入,在一行中输出 ERROR: X is not a legal number,其中 X 是输入。最后在一行中输出结果:The average of K numbers is Y,其中 K 是合法输入的个数,Y 是它们的平均值,精确到小数点后 2 位。如果平均值无法计算,则用 Undefined 替换 Y。如果 K 为 1,则输出 The average of 1 number is Y。
  • 输入样例 1:
    7
    5 -3.2 aaa 9999 2.3.4 7.123 2.35
    
  • 输出样例 1:
    ERROR: aaa is not a legal number
    ERROR: 9999 is not a legal number
    ERROR: 2.3.4 is not a legal number
    ERROR: 7.123 is not a legal number
    The average of 3 numbers is 1.38
    
  • 输入样例 2:
    2
    aaa -9999
    
  • 输出样例 2:
    ERROR: aaa is not a legal number
    ERROR: -9999 is not a legal number
    The average of 0 numbers is Undefined
    

如果使用sscanf和sprintf函数,这题会很简单。
sscanf() – 从一个字符串中读进与指定格式相符的数据
sprintf() – 字符串格式化命令,主要功能是把格式化的数据写入某个字符串中
如果(ˇˍˇ)想知道效果,可以添加一行打印语句。

  • 不使用这两个函数而是自己检查字符串是否合法的方法:
    他人的文章:https://blog.csdn.net/hy971216/article/details/80653566。(不过在转换字符串为浮点数时使用了库函数atof(ascii to float)。
//input
7
5 -3.2 aaa 9999 2.3.4 7.123 2.35
//output
5 = 5 = 5.00
-3.2 = -3.2 = -3.20
aaa = -3.2 = -3.20
ERROR: aaa is not a legal number
9999 = 9999 = 9999.00
ERROR: 9999 is not a legal number
2.3.4 = 2.3 = 2.30
ERROR: 2.3.4 is not a legal number
7.123 = 7.123 = 7.12
ERROR: 7.123 is not a legal number
2.35 = 2.35 = 2.35
The average of 3 numbers is 1.38
#include <cstdio>
#include <iostream>
using namespace std;
int main() {
	int N, cnt = 0;
	double sum = 0.0, temp;
	char a[100], b[100]; 
	scanf("%d", &N);
	while (N--) {
		scanf("%s", a);
		sscanf(a, "%lf", &temp); //从字符数组a中以浮点数的形式读入 
		//cout << a << " = " << temp << " = ";
		sprintf(b, "%.2lf", temp); //将浮点数temp以小数点后两位精度的形式输出到字符数组b中 
		//cout << b << endl;
		int flag = 0;
		for (int j = 0; a[j]; j++) //以s为基准 
			if (a[j] != b[j]) flag = 1; 
		if (flag || temp > 1000 || temp < -1000) {
            printf("ERROR: %s is not a legal number\n", a);
            continue;
		} else {
			sum += temp;
			cnt++;
		}
	}
	if (cnt == 1) printf("The average of 1 number is %.2lf", sum);
	else if (cnt > 0) printf("The average of %d numbers is %.2lf", cnt, sum / cnt);
	else printf("The average of 0 numbers is Undefined");
	return 0;	
}

B1055 集体照(25 分)

拍集体照时队形很重要,这里对给定的 N 个人 K 排的队形设计排队规则如下:
每排人数为 N/K(向下取整),多出来的人全部站在最后一排;
后排所有人的个子都不比前排任何人矮
每排中最高者站中间(中间位置为 m/2+1,其中 m 为该排人数,除法向下取整);
每排其他人以中间人为轴,按身高非增序,先右后左交替入队站在中间人的两侧(例如5人身高为190、188、186、175、170,则队形为175、188、190、186、170。这里假设你面对拍照者,所以你的左边是中间人的右边);
多人身高相同,则按名字的字典序升序排列。这里保证无重名。

现给定一组拍照人,请编写程序输出他们的队形。

  • 输入格式:
    每个输入包含 1 个测试用例。每个测试用例第 1 行给出两个正整数 N(≤10​4​​,总人数)和 K(≤10,总排数)。随后 N 行,每行给出一个人的名字(不包含空格、长度不超过 8 个英文字母)和身高([30, 300] 区间内的整数)。
  • 输出格式:
    输出拍照的队形。即K排人名,其间以空格分隔,行末不得有多余空格。注意:假设你面对拍照者,后排的人输出在上方,前排输出在下方。
  • 输入样例:
    10 3
    Tom 188
    Mike 170
    Eva 168
    Tim 160
    Joe 190
    Ann 168
    Bob 175
    Nick 186
    Amy 160
    John 159
    
  • 输出样例:
    Bob Tom Joe Nick
    Ann Mike Eva
    Tim Amy John
    

既然后排的人个子都更高,而且先输出后排,那么先按照身高降序排序,身高相同的再按名字升序排序。排队的过程就是填充vector的过程,和输出图形一样的方法。

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
struct node {
	string name; 
	int h; //身高 
};
bool cmp(const node &a, const node &b) { //后排的人输出在上方,前排输出在下方
	return a.h != b.h ? a.h > b.h : a.name < b.name; //先取高的后排 
} 
int main() {
	int n, k, m; //总人数 K排 每排人数 
	cin >> n >> k;
	vector<node> queue(n);
	for (int i = 0; i < n; i++) {
		cin >> queue[i].name >> queue[i].h;
	}
	sort(queue.begin(), queue.end(), cmp);
	int row = k, t = 0; //k排
	while (row) {
		if (row == k) m = n - n / k * (k - 1); //最后一排的人数
		else m = n / k;
		vector<string> ans(m);
        ans[m / 2] = queue[t].name; //第一个高的
		// 左边一列 
		int j = m / 2 - 1;
		for (int i = t + 1; i < t + m; i = i + 2) {
			ans[j--] = queue[i].name;
		} 
		//右边一列
		j = m / 2 + 1;
		for (int i = t + 2; i < t + m; i = i + 2) {
			ans[j++] = queue[i].name;
		} 
		//输出当前列
		cout << ans[0];
		for (int i = 1; i < m; i++) cout << " " << ans[i];
		cout << endl; 
		t = t + m;
		row--;
	} 
	return 0;
} 

B1056 组合数的和 (15 分)

组合的数字的可能太多了,为O((n-1)n)。

#include <cstdio>
int combine(int a, int b) { //a十位 b个位
    return a * 10 + b;
}

int main() {
    int n;
    scanf("%d", &n);
    int a[n];
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);
    int sum = 0;
    for (int i = 0; i < n; i++) {
        int t = a[i];
        for (int j = 0; j < n; j++) {
            if (a[j] != t) sum += combine(t, a[j]);
        }
    }
    printf("%d\n", sum);
    return 0;
}

B1057 数零壹 (20 分)

部分正确 19,这里使用了特殊的存入一行字符串的代码;另外使用hash计算sum二进制表示中0、1的个数。

#include <cstdio>
char s[100010];
int main() {
    scanf("%[^\n]", s); //不存换行符
    int sum = 0;
    for (int i = 0; s[i]; i++) {
        if (s[i] >= 'A' && s[i] <= 'Z') sum += s[i] - 'A' + 1;
        else if (s[i] >= 'a' && s[i] <= 'z') sum += s[i] - 'a' + 1;
    }
    int hash[2] = {0};
    do {
        hash[sum % 2]++;
        sum /= 2;
    } while (sum != 0);
    printf("%d %d\n", hash[0], hash[1]);
    return 0;
}

正确代码:使用了ctype.h中的字符测试函数;当sum为0的时候,说明该字符串中没有任何英文字母,应该特判为"0 0"

#include <cstdio>
#include <cctype>
char s[100010];
int main() {
    scanf("%[^\n]", s);
    int sum = 0;
    for (int i = 0; s[i]; i++) {
        if (isupper(s[i])) sum += s[i] - 'A' + 1;
        else if (islower(s[i])) sum += s[i] - 'a' + 1;
    }
    if (sum == 0) printf("0 0\n");
    else {
    	int hash[2] = {0};
	    do {
	        hash[sum % 2]++;
	        sum /= 2;
	    } while (sum != 0);
	    printf("%d %d\n", hash[0], hash[1]);
	}
    return 0;
}

B1058 选择题 (20 分)

自己这么写字符数组太麻烦了。这里比较学生的答案对不对,是将学生的答案选项(字典序)连接成字符串,与正确答案(字典序)字符串比较。这个题目的难题版本在B1073 多选题常见计分法

#include <cstring>
#include <cstdio>
struct choice {
	int score;
	char right[10];
} answer[101];
struct student {
	int total;
	char ch[10];
};
int error[101] = {0}, max = 0; //每道题的错误次数 

int main() {
	int n, m; scanf("%d%d", &n, &m); //N ≤1000和M ≤100
	int num, rightNum; //选项个数 正确选项个数 
	for (int i = 1; i <= m; i++) {	
		scanf("%d%d%d", &answer[i].score, &num, &rightNum);
		int j; 
		for (j = 0; j < rightNum; j++) {
			getchar();
			scanf("%c", &answer[i].right[j]);
		}
		answer[i].right[j] = '\0';
	}
	getchar(); //吸收换行符 
	struct student t; 
	for (int i = 1; i <= n; i++) {
 		t.total = 0;
 		for (int k = 1; k <= m; k++) { //题目顺序
 			scanf("(%d", &num); 
	 		int j; 
			for (j = 0; j < num; j++) {
				getchar();
				scanf("%c", &t.ch[j]);
			}
			t.ch[j] = '\0';
			getchar(); getchar(); //吸收后括号和空格/换行符	
			if (strcmp(t.ch, answer[k].right) == 0) t.total += answer[k].score;
			else error[k]++; //错误次数加1
        }
		printf("%d\n", t.total);
	} 
	for (int i = 0; i <= 100; i++) {
		if (error[i] > max) max = error[i];
	} 
	if (max == 0) printf("Too simple\n"); //所有题目都没有人错 
	else {
		printf("%d", max);
		for (int i = 0; i <= 100; i++) {
			if (error[i] == max) printf(" %d", i);
		}
	}
	return 0;
}

B1059 C语言竞赛 (20 分)

速度比较慢,但是都能通过:

#include <cstdio>
const int maxn = 10100;
bool p[maxn] = {false};
void findPrime(int n) {
	for (int i = 2; i < maxn; i++) {
		if (i > n) break;
		if (p[i] == false) {
			for (int j = i + i; j < maxn; j += i) {
				p[j] = true;
			}
		}	
	}
}
char award[][25] = {"Mystery Award", "Minion", "Chocolate"};

struct person {
	int id;
	int type;
	bool checked;
	person() { checked = false;}
} a[maxn];

int main() {
	int n;
	scanf("%d", &n);
	findPrime(n);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i].id);
		if (i == 1) a[i].type = 0;
		else if (p[i] == false) a[i].type = 1; //排名为素数的学生将赢得黄人玩偶
		else a[i].type = 2;
	}
	int k, t;
	scanf("%d", &k);
	for (int i = 0; i < k; i++) {
		scanf("%d", &t);
		int flag = 1, j = 1;
		while (j <= n && a[j].id != t) j++; 
		if (j > n) {
			printf("%04d: Are you kidding?\n", t);
			continue;
		}
	    if (a[j].checked == false) {
			printf("%04d: %s\n", t, award[a[j].type]);
			a[j].checked = true;
		} else printf("%04d: Checked\n", t);
	}
	return 0;
}

☆ B1060 爱丁顿数 (25 分)

英国天文学家爱丁顿很喜欢骑车。据说他为了炫耀自己的骑车功力,还定义了一个“爱丁顿数” E ,即满足有E天骑车超过E英里的最大整数E。据说爱丁顿自己的E等于87。
现给定某人N天的骑车距离,请你算出对应的爱丁顿数E(≤N)。

  • 输入格式:输入第一行给出一个正整数 N (≤10​5​​),即连续骑车的天数;第二行给出 N 个非负整数,代表每天的骑车距离。
  • 输出格式:在一行中给出 N 天的爱丁顿数。
  • 输入样例:
    10
    6 7 6 9 3 10 8 2 7 8
    
  • 输出样例:
    6
    

这题写起来简单,但是想通就有点难。

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 100000;
int a[maxn];
int main() {
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);
    sort(a, a + n);

    int E = 0;
    for (int i = 0; i < n; i++) {
        if (a[i] > n - i) { //a[i]超过从n-i(a[i]到最后的项数), 即满足有n-i天骑车超过n-1英里
            E = n - i;
            break;
        }
    }
    printf("%d\n", E);
    return 0;
}

B1061 判断题 (15 分)

#include <cstdio>

int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    int fullscore[m], right[m];
    for (int i = 0; i < m; i++) scanf("%d", &fullscore[i]);
    for (int i = 0; i < m; i++) scanf("%d", &right[i]);
    for (int i = 0; i < n; i++) {
    	int sum = 0, t;
        for (int j = 0; j < m; j++) {
            scanf("%d", &t);
            sum += t == right[j] ? fullscore[j] : 0;
        }
        printf("%d\n", sum);
    }
    return 0;
}

B1062 最简分数 (20 分)

一个分数一般写成两个整数相除的形式:N/M,其中 M 不为0。最简分数是指分子和分母没有公约数的分数表示形式。现给定两个不相等的正分数 N​1​​/M​1​​ 和 N​2​​/M​2​​,要求你按从小到大的顺序列出它们之间分母为 K 的最简分数

  • 输入格式:输入在一行中按 N/M 的格式给出两个正分数,随后是一个正整数分母 K,其间以空格分隔。题目保证给出的所有整数都不超过 1000。
  • 输出格式:在一行中按 N/M 的格式列出两个给定分数之间分母为 K 的所有最简分数,按从小到大的顺序,其间以 1 个空格分隔。行首尾不得有多余空格。题目保证至少有 1 个输出。
  • 输入样例:
    7/18 13/20 12
    
  • 输出样例:
    5/12 7/12
    

关键点在于第一个分数不一定小于第二个分数,这个时候就该调换两个给定分数,以便从小数到大。不然会有一个测试点错误。下面这个方法是为了熟悉分数类的写法,但是通过最后一个测试点时,耗时长。

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
ll gcd(ll a, ll b) { //求a与b的最大公约数 
	return !b ? a : gcd(b, a % b); 
}
ll lcm(ll a, ll b) { //求a与b的最小公倍数 
    return a / gcd(a, b) * b;
}
struct Fraction { //最简洁的写法, 假分数 
	ll up, down; //分子分母 
} a, b;

Fraction reduction(Fraction result) { //化简, 仅仅针对正分子
	//如果分子不为0, 约分 
	int d = gcd(abs(result.up), abs(result.down)); //分子分母的最大公约数 
	result.up /= d;  //约去最大公约数 
	result.down /= d;
	return result; 
} 

int main() {
	ll K; //N/M的格式给出两个正分数 正整数分母K
	scanf("%lld/%lld %lld/%lld %lld", &a.up, &a.down, &b.up, &b.down, &K);
	if (a.up * b.down > b.up * a.down) swap(a, b); //a>b时调换 
	
	ll threelcm = lcm(lcm(a.down, b.down), K); //三分母最小公倍数, 用来通分	
	Fraction t1, t2;
	int len = 0; 
	a.up *= (threelcm / a.down); 
	b.up *= (threelcm / b.down);
	a.down = b.down = t1.down = threelcm; //通分
	int flag = false;
	for (ll i = a.up + 1; i < b.up; i++) {
		t1.up = i; //枚举每一个可能的分子 
		t2 = reduction(t1); //化简
		if (t2.down == K) { //是分母为K的最简分数
			printf("%s%lld/%lld", flag ? " " : "", t2.up, t2.down); //输出最简分数 
			flag = true;
		}
	} 
	return 0;
}

第二个方法如下:n1/m1,n2/m2。因为要列出n1/m1和n2/m2之间的最简分数,但是n1/m1不一定小于n2/m2,所以如果n1 * m2 > n2 * m1,说明n1/m1比n2/m2大,则调换n1和n2、m1和m2的位置

假设所求的分数为up/k,先令up=1,当n1 * k >= m1 * up时,up不断++,直到up符合n1/m1 < up/k为止。然后在n1/m1和n2/m2之间找符合条件的up的值用gcd(up, k)是否等于1判断up和k是否有最大公约数,如果等于1表示没有最大公约数,即此时为最简,就输出up/k,然后up不断++直到退出循环。

#include <iostream>
using namespace std;
int gcd(int a, int b){
    return b == 0 ? a : gcd(b, a % b);
}
int main() {
    int n1, m1, n2, m2, k;
    scanf("%d/%d %d/%d %d", &n1, &m1, &n2, &m2, &k);
    if(n1 * m2 > n2 * m1) { //a>b时调换
        swap(n1, n2); //没有分数类, 所以分子和分母分别调换
        swap(m1, m2);
    }
    int up = 1;
    bool flag = false;
    while (n1 * k >= m1 * up) up++;
    while (n1 * k < m1 * up && m2 * up < n2 * k) { //分数间比较大小
        if(gcd(up, k) == 1) {
            printf("%s%d/%d", flag == true ? " " : "", up, k);
            flag = true;
        }
        up++;
    }
    return 0;
}

★(取整函数) B1063 计算谱半径 (20 分)

这里本想使用round函数的,但是全部错误,发现我对这几个函数的使用不太清楚:

  • double floor(double):返回不大于x的最大整数值(返回为double型),向下取整;
  • double ceil(double):返回不小于x的最小整数值(返回为double型),向上取整;
  • double round(double):返回x的四舍五入整数值(返回为double型,其实就是一个整数值)。等价于:
    double round(double x) {
    	return x > 0.0 ? florr(x + 0.5) : ceil(x - 0.5);
    }
    
#include <cstdio>
#include <cmath>

int main() {
    int n;
    scanf("%d", &n);
    int real, imag;
    double max = 0.0;
    while (n--) {
        scanf("%d%d", &real, &imag);
        double t = sqrt(1.0 * real * real + 1.0 * imag * imag);
        if (t > max) max = t;
    }
    printf("%.2lf", max);
    return 0;
}

B1064 朋友数 (20 分)

简单整值映射。

#include <cstdio>
const int maxn = 10010;
int hash[maxn] = {0};
int main() {
    int n;
    scanf("%d", &n);
    int t, friendidNum = 0; 
    for (int i = 0; i < n; i++) {
        scanf("%d", &t);
        int friendid = 0;
        while (t) {
            friendid += t % 10;
            t /= 10;
        }
        if (hash[friendid] == 0) friendidNum++;
        hash[friendid]++;
    }
    printf("%d\n", friendidNum);
    for (int i = 0; i < maxn; i++) {
        if (hash[i] != 0)  {
            printf("%d", i); //输出朋友证号
            friendidNum--;
            if (friendidNum > 0) printf(" ");
        }
    }
    printf("\n");
    return 0;
}

B1065 单身狗 (25 分)

部分正确 21,测试点3答案错误。后来发现ID要是五位数的(从00000-99999),我忘记把没有五位的数字输出为(0填充)五位了

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

const int maxn = 100100;
int cp[maxn]; //00000到99999
int main() {
	memset(cp, -1, sizeof(cp));
    int n, p1, p2;
    scanf("%d", &n);
    while (n--) {
        scanf("%d%d", &p1, &p2);
        cp[p1] = p2;
        cp[p2] = p1;
    }
    
    int m; scanf("%d", &m); //≤10000
    int people[m];
    for (int i = 0; i < m; i++){
        scanf("%d", &people[i]);
    }
    int sinNum = 0, sinPerson[m + 10];
    for (int i = 0; i < m; i++) {
    	if (cp[people[i]] == -1) sinPerson[sinNum++] = people[i]; 
    	//伴侣对中没有这个人, 说明是单身狗 
    	else {
            int t = cp[people[i]], j = 0; //有伴侣找找伴侣来了没有 
    		while (j < m && people[j] != t) j++;
			if (j >= m) sinPerson[sinNum++] = people[i]; //他的伴侣没来, 落单 
        }
    }
    sort(sinPerson, sinPerson + sinNum);
    printf("%d\n", sinNum); 
    for (int i = 0; i < sinNum; i++) {
    	if (i > 0) printf(" ");
        printf("%05d", sinPerson[i]);
        
    }
    // printf("\n");
    return 0;
}

B1066 图像过滤 (15 分)

#include <cstdio>

int main() {
    int m, n, a, b, grey;
    scanf("%d%d%d%d%d", &m, &n, &a, &b, &grey);
    int data[n];
    while (m--) {
        for (int i = 0; i < n; i++) scanf("%d", &data[i]);
        for (int i = 0; i < n; i++) {
            if (i > 0) printf(" ");
            if (data[i] >= a && data[i] <= b) printf("%03d", grey);
            else printf("%03d", data[i]);
        }
        printf("\n");
    }
    return 0;
}

★(行输入总结) B1067 试密码 (20 分)

部分正确 19:坑的是:要getchar();以后要用getline(我已经不想再用fgets了……几次都坑在这里),这里的问题比较隐蔽,当人们输入密码没有满次数又不想输了,“#”直接退出,而这里我使用字符串比较函数,其实是将输入的"#\n"与”#“比较(没有及时处理fgets输入的字符串末尾的换行符),因此不会退出,而是输出错误提示?。
总结一下目前已知的合理输入一行的方法:

  1. 直接使用C,用gets,遇到换行符将之吸收换成结束符,文件末尾输入结束返回NULL;
  2. 使用C++,搭配fgets(char [], maxSize, stdin),文件末尾输入结束返回NULL,自己处理末尾多出来的换行符
  3. 使用C++,搭配cin和字符数组,cin.getline(char [], maxSize)
  4. 使用C++,搭配cin和string,getline(cin, string)
  5. 使用C/C++,搭配scanf和它的正则表达式用法:scanf("%[^\n]", char []),相关链接:https://www.cnblogs.com/orange1438/archive/2013/05/12/4544958.html,讲得很清楚了。
#include <cstdio>
#include <cstring>
int main() {
    int n, cnt = 0;
    char password[22], tryPass[100];
    scanf("%s%d", password, &n);
    getchar(); //吸收掉换行符 
    while (fgets(tryPass, 100, stdin) != NULL) {
        if (strcmp(tryPass, "#") == 0) break;
        if (cnt++ == n) {
            printf("Account locked\n"); 
            break;
        }
        int len = strlen(tryPass);
        if (tryPass[len - 1] == '\n') tryPass[len - 1] = '\0';
        if (strcmp(password, tryPass) == 0) {
            printf("Welcome in\n"); 
            break; 
        } else printf("Wrong password: %s\n", tryPass);
    }
    return 0;
}

正确代码:

#include <cstdio>
#include <cstring>
int main() {
    int n, cnt = 0;
    char password[22], tryPass[100];
    scanf("%s%d", password, &n);
    getchar(); //吸收掉换行符 
    while (fgets(tryPass, 100, stdin) != NULL) {
        if (cnt++ >= n) { puts("Account locked"); break; } //满次数直接退出
        int len = strlen(tryPass);
        if (tryPass[len - 1] == '\n') tryPass[len - 1] = '\0';
        if (strcmp(tryPass, "#") == 0) break; 
        
        if (strcmp(password, tryPass) == 0) { puts("Welcome in"); break; } 
        else printf("Wrong password: %s\n", tryPass);
    }
    return 0;
}

★★ B1068 万绿丛中一点红 (20 分)

对于计算机而言,颜色不过是像素点对应的一个 24 位的数值。现给定一幅分辨率为 M×N 的画,要求你找出万绿丛中的一点红,即有独一无二颜色的那个像素点,并且该点的颜色与其周围 8 个相邻像素的颜色差充分大

  • 输入格式:
    输入第一行给出三个正整数,分别是 M 和 N(≤ 1000),即图像的分辨率;以及 TOL,是所求像素点与相邻点的颜色差阈值,色差超过 TOL 的点才被考虑。随后 N 行,每行给出 M 个像素的颜色值,范围在 [0,2​24​​) 内。所有同行数字间用空格或 TAB 分开。
  • 输出格式:
    在一行中按照 (x, y): color 的格式输出所求像素点的位置以及颜色值,其中位置 x 和 y 分别是该像素在图像矩阵中的列、行编号(从 1 开始编号)。如果这样的点不唯一,则输出 Not Unique;如果这样的点不存在,则输出 Not Exist。
  • 输入样例 1:
8 6 200
0 	 0 	  0 	   0	    0 	     0 	      0        0
65280 	 65280    65280    16711479 65280    65280    65280    65280
16711479 65280    65280    65280    16711680 65280    65280    65280
65280 	 65280    65280    65280    65280    65280    165280   165280
65280 	 65280 	  16777015 65280    65280    165280   65480    165280
16777215 16777215 16777215 16777215 16777215 16777215 16777215 16777215
  • 输出样例 1:
    (5, 3): 16711680
    
  • 输入样例 2:
    4 5 2
    0 0 0 0
    0 0 3 0
    0 0 0 0
    0 5 0 0
    0 0 0 0
    
  • 输出样例 2:
    Not Unique
    
  • 输入样例 3:
    3 3 5
    1 2 3
    3 4 5
    5 6 7
    
  • 输出样例 3:
    Not Exist
    

部分正确 17,清楚问题所在。这里的错误思路其实是暴力检查每一个点与周围点的颜色差,记录充分大的那些点,然后统计它们的个数,没有就是不存在;如果存在且只存在一个点出现次数为1,就是答案;如果有多个点出现次数为1就Not Unique……虽然我只放了检查中间的代码上来,而且后面的统计也不是那样写的…但至少还有17分(笑)…

不过其实,这里对题意的理解也有错误,比如与其周围相邻像素的颜色差,还可能是负数,因此这种暴力膜的想法就不可取了,要添加好些绝对值函数调用……太麻烦了,不如重新写。

#include <cstdio>
const int maxn = 1010;
int a[maxn][maxn] = {0};
int main() {
    int m, n, tol;
    scanf("%d%d%d", &m, &n, &tol);
    for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) scanf("%d", &a[i][j]);
    int x, y, color, sum = 0; //j+1 i+1
    for (int i = 0; i < n; i++) { //n行 
    	for (int j = 0; j < m; j++) { //m列 
    		int flag = 0, t = a[i][j] - tol; //检查每一个点 
    		if ((i != 0 && i != n - 1) && (j != 0 && j != m - 1)) { //检查中间 
    			if (t > a[i-1][j-1] && t > a[i-1][j] && t > a[i-1][j+1] &&
				    t > a[i][j-1] && t > a[i][j+1] && 
					t > a[i+1][j-1] && t > a[i+1][j] && t > a[i+1][j+1]) flag = 1; 
			}
			if (flag) {
				sum++; color = a[i][j];
				x = j + 1, y = i + 1;
			}
		}
	}
	if (sum == 1) printf("(%d, %d): %d\n", x, y, color);
	else if (sum == 0) printf("Not Exist\n");
	else if (sum > 1) printf("Not Unique\n"); 
    return 0;
}

换种角度,先找到独一无二的点,再判断是不是色差充分大。如果没有独一无二的点或者色差不够大,Not Exist;有几个,Not Unique……这里用map的key表示数字,value来表示key出现的次数。用8*2个偏移值表示不同的方向,用函数抽出共同的逻辑,这一点值得借鉴。

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

int n, m, tol; //M×N
int s[1001][1001];
map<int, int> vis; //八个方向
int dir[8][2] = {1,0, -1,0, 0,1, 0,-1, 1,1, 1,-1, -1,1, -1,-1};
//判断是否大于阈值
bool check(int x, int y) {
    for (int i = 0; i < 8; i++) {
        int xx = x + dir[i][0], yy = y + dir[i][1];
        if (xx >= 0 && xx < n && yy < m && yy >= 0 && 
        abs(s[xx][yy] - s[x][y]) <= tol) 
            return false;
    }
    return true;
}
int main() {
    cin >> m >> n >> tol;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> s[i][j];
            vis[s[i][j]]++;
        }
    }
    //cnt记录只出现1次的数字的个数
    //x y记录坐标
    int cnt = 0, x, y;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (vis[s[i][j]] == 1 && check(i, j)) {
                cnt++;
                x = i; y = j;
            }
        }
    }
    if (cnt == 1) printf("(%d, %d): %d\n",y+1, x+1, s[x][y]);
	else if (cnt > 1) puts("Not Unique");
    else puts("Not Exist");
	
	return 0;
}

B1069 微博转发抽奖 (20 分)

微博的网友的昵称(不超过 20 个字符、不包含空格回车的非空字符串)太长,自己哈希很麻烦,而且要判断处于当前中奖位置的网友中过奖没有……所以使用了map,插入同样的键时会覆盖掉以前的记录,使用set也可以(自动去重排序)。另外,发现vector可以有多种初始化方式,包括给出初始大小,设定初值等。

#include <cstdio>
#include <map>
#include <iostream>
#include <string>
#include <vector>
using namespace std;

int main() {
	int m, n, s;
	scanf("%d%d%d", &m, &n, &s);
	map<string, int> mp;
	vector<string> people(m); //m个元素的变长数组 
	for (int i = 0; i < m; i++) {
		cin >> people[i];
		mp.insert(make_pair(people[i], 0)); //没有访问过 
	}
	int cnt = 0; //中奖人数
	for (int i = s - 1; i < m; ) {
		if (mp[people[i]] == 0) {
			cnt++; //中奖人数+1 
			cout << people[i] << endl;
			mp[people[i]] = 1; 
			i += n; //查找下一个中奖间隔的人 
		} else i++; 
	} 
	if (!cnt) printf("Keep going...\n");
	return 0;
}

☆ B1070 结绳 (25 分)

给定一段一段的绳子,你需要把它们串成一条绳。每次串连的时候,是把两段绳子对折,再如下图所示套接在一起。这样得到的绳子又被当成是另一段绳子,可以再次对折去跟另一段绳子串连。每次串连后,原来两段绳子的长度就会减半。
给定N段绳子的长度,你需要找出它们能串成的绳子的最大长度

  • 输入格式:
    每个输入包含1个测试用例。每个测试用例第1行给出正整数N (2 <= N <= 104);第2行给出N个正整数,即原始绳段的长度,数字间以空格分隔。所有整数都不超过104
  • 输出格式:
    在一行中输出能够串成的绳子的最大长度。结果向下取整,即取为不超过最大长度的最近整数。
  • 输入样例:
    8
    10 15 12 3 4 13 1 15
    
  • 输出样例:
    14
    

分析:因为所有长度都要串在一起,每次都等于(旧的绳子长度+新的绳子长度)/2,所以越是早加入绳子长度中的段,越要对折的次数多,所以既然希望绳子长度是最长的,就必须让长的段对折次数尽可能的短。所以将所有段从小到大排序,然后从头到尾从小到大分别将每一段依次加入结绳的绳子中,最后得到的结果才会是最长的结果。

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

int main() {
    int n;
    scanf("%d", &n);
    int a[n];
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);
    sort(a, a + n);
    int max = a[0];
    for (int i = 1; i < n; i++) {
        max = (max + a[i]) / 2;
    }
    printf("%d\n", (int)max);
    return 0;
}

B1071 小赌怡情 (15 分)

#include <cstdio>

int main() {
    int token, n;
    scanf("%d%d", &token, &n);
    //n1和n2是先后给出的两个[0, 9]内的整数, 两个数字不相等; 
    //b为0表示赌小, 为1表示玩家赌大; t表示下注的筹码数
    int n1, b, t, n2; 
    while (n--) {
        scanf("%d%d%d%d", &n1, &b, &t, &n2);
        if (t > token) { 
            printf("Not enough tokens.  Total = %d.\n", token);
            continue;
        } 
        if ((!b && n2 < n1) || (b && n2 > n1)) { //赌小赌赢了/赌大赌赢了
            token += t;
            printf("Win %d!  Total = %d.\n", t, token);
            continue;
        } else { //赌输了
            token -= t;
            printf("Lose %d.  Total = %d.\n", t, token);
            if (token == 0) { //赌输输光
                printf("Game Over.\n");
                break;
            }
        } 
    }
    return 0;
}

B1072 开学寄语 (20 分)

需要注意的是,如果一个学生没有任何违规物品,那么就不用输出任何信息,包括换行符。不然四个测试点都是格式错误。

#include <cstdio>
struct student {
    char name[6];
    int num, stuffs[12];
};
int n, m;
int findban(int x, int ban[]) {
	for (int i = 0; i < m; i++) {
		if (ban[i] == x) return 1;
	}
	return 0; 
}
int main() {
    scanf("%d%d", &n, &m);
    int banStuff[m];
    for (int i = 0; i < m; i++) scanf("%d", &banStuff[i]);
    struct student t;
    int number = 0, legalStuffs = 0; //违规学生人数/被查缴物品的总数 
    for (int i = 0; i < n; i++) {
        scanf("%s%d", t.name, &t.num);
        int cnt = 0;
        for (int j = 0; j < t.num; j++) {
        	scanf("%d", &t.stuffs[j]);
        	if (findban(t.stuffs[j], banStuff) == 1) {
        		legalStuffs++; //被查缴物品的总数+1
        		if (cnt == 0) {
        			printf("%s: %04d", t.name, t.stuffs[j]);
        			number++; cnt++; //违规学生人数+1
				} else printf(" %04d", t.stuffs[j]);
			}
		}
        if (cnt) printf("\n"); //如学生没有任何违规物品,就不用输出任何信息,包括换行符
    }
    printf("%d %d\n", number, legalStuffs);
    return 0;
}

★★★ B1073 多选题常见计分法 (20 分)

有一种最常见的计分方法是:如果考生选择了部分正确选项,并且没有选择任何错误选项,则得到 50% 分数;如果考生选择了任何一个错误的选项,则不能得分。本题就请你写个程序帮助老师批改多选题,并且指出哪道题的哪个选项错的人最多。

  • 输入格式:
    输入在第一行给出两个正整数 N(≤1000)和 M(≤100),分别是学生人数和多选题的个数。随后 M 行,每行顺次给出一道题的满分值(不超过 5 的正整数)、选项个数(不少于 2 且不超过 5 的正整数)、正确选项个数(不超过选项个数的正整数)、所有正确选项。注意每题的选项从小写英文字母 a 开始顺次排列。各项间以 1 个空格分隔。最后 N 行,每行给出一个学生的答题情况,其每题答案格式为 (选中的选项个数 选项1 ……),按题目顺序给出。注意:题目保证学生的答题情况是合法的,即不存在选中的选项数超过实际选项数的情况。
  • 输出格式:
    按照输入的顺序给出每个学生的得分,每个分数占一行,输出小数点后 1 位。最后输出错得最多的题目选项的信息,格式为:错误次数 题目编号(题目按照输入的顺序从1开始编号)-选项号。如果有并列,则每行一个选项,按题目编号递增顺序输出;再并列则按选项号递增顺序输出。行首尾不得有多余空格。如果所有题目都没有人错,则在最后一行输出 Too simple。
  • 输入样例 1:
    3 4 
    3 4 2 a c
    2 5 1 b
    5 3 2 b c
    1 5 4 a b d e
    (2 a c) (3 b d e) (2 a c) (3 a b e)
    (2 a c) (1 b) (2 a b) (4 a b d e)
    (2 b d) (1 e) (1 c) (4 a b c d)
    
  • 输出样例 1:
    3.5
    6.0
    2.5
    2 2-e
    2 3-a
    2 3-b
    
  • 输入样例 2:
    2 2 
    3 4 2 a c
    2 5 1 b
    (2 a c) (1 b)
    (2 a c) (1 b)
    
  • 输出样例 2:
    5.0
    5.0
    Too simple
    

这是使用了位运算的版本,a-e表示为1, 2, 4, 8, 16。如ac,则表示为1+4。其实就是00000,每位选不选的意思。如果考生的答案与正确答案完全相同,则异或的结果为0;

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
int main() {
    int n, m, optnum, truenum, temp, maxcnt = 0;
    int hash[] = {1, 2, 4, 8, 16}, opt[1010][110] = {0};
    char c;
    scanf("%d %d", &n, &m);
    vector<int> fullscore(m), trueopt(m); //记录每道题的满分和正确选项
    vector<vector<int>> cnt(m, vector<int>(5)); //每道题的5个选项分别错的次数
    for (int i = 0; i < m; i++) {
        scanf("%d %d %d", &fullscore[i], &optnum, &truenum);
        for (int j = 0; j < truenum; j++) {
            scanf(" %c", &c);
            trueopt[i] += hash[c-'a']; //叠加答案选项
        }
    }
    for (int i = 0; i < n; i++) {
        double grade = 0; //记录每个学生的成绩
        for (int j = 0; j < m; j++) {
            getchar();
            scanf("(%d", &temp);
            for (int k = 0; k < temp; k++) {
                scanf(" %c)", &c);
                opt[i][j] += hash[c-'a'];
            }
            int el = opt[i][j] ^ trueopt[j];
            if (el) { //不为0
                if ((opt[i][j] | trueopt[j]) == trueopt[j]) { //漏选
                    grade += fullscore[j] * 1.0 / 2;
                }
                if (el) { //记录错选漏选的选项的次数
                    for (int k = 0; k < 5; k++)
                        if (el & hash[k]) cnt[j][k]++;
                }
            } else { //为0, 得满分
                grade += fullscore[j];
            }
        }
        printf("%.1f\n", grade);
    }
    for (int i = 0; i < m; i++)
        for (int j = 0; j < 5; j++) //记录错得最多的选项的次数
            maxcnt = maxcnt > cnt[i][j] ? maxcnt : cnt[i][j];
    
    if (maxcnt == 0) { //记录每个学生的成绩
        printf("Too simple\n");
    } else {
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < cnt[i].size(); j++) {
                if (maxcnt == cnt[i][j]) //记录每个学生的成绩
                    printf("%d %d-%c\n", maxcnt, i+1, 'a'+j);
            }
        }
    }
    return 0;
}

(大整数) B1074 宇宙无敌加法器 (20 分)

N位的进制表(0 < N ≤ 20),以回车结束。 随后两行,每行给出一个不超过N位的非负的PAT数。PAT数可能越过long long的界了,保险起见使用大整数的模版,把加法操作改一下就可以了。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 25;
struct bign {
	int d[N], len;
	bign() {
		memset(d, 0, sizeof(d));
        len = 0;
	}
};

bign change(char s[]) {
    bign a;
	a.len = strlen(s);
	for (int i = 0; s[i]; i++) {
		a.d[i] = s[a.len - i - 1] - '0';
	} 
	while (a.len > 1 && a.d[a.len - 1] == 0) a.len--; //去掉多余的零
	return a;
}
bign list;
bign patAdd(bign a, bign b) {
	bign t;
	int carry = 0;
	for (int i = 0; i < a.len || i < b.len; i++) {
		int r = (carry + a.d[i] + b.d[i]);
		if (list.d[i] != 0) {
			t.d[t.len++] = r % list.d[i];
			carry = r / list.d[i];
		} else {
		    t.d[t.len++] = r % 10;
			carry = r / 10;	
		}
	}
	if (carry) t.d[t.len++] = carry;
	return t;
}

bign print(bign a) {
	for (int i = a.len - 1; i >= 0; i--) {
		printf("%d", a.d[i]);
	}
	printf("\n"); 
}

int main() {
	char s[N], a[N], b[N];
	scanf("%s%s%s", s, a, b);
	list = change(s);
	bign A = change(a), B = change(b);
	bign ans = patAdd(A, B);
	print(ans);
	return 0;
}

B1075 链表元素分类 (25 分)


B1076 Wifi密码 (15 分)

#include <cstdio>

int main() {
    int n;
    scanf("%d", &n);
    int res[n], size = 0;
    getchar();  吸收换行符 
    char id, ans;
    while (n--) {
        for (int i = 0; i < 4; i++) {
            scanf("%c-%c", &id, &ans);
            getchar();  //吸收空格或换行符 
            if (ans == 'T') res[size++] = id - 'A' + 1;
        }
    }
    for (int i = 0; i < size; i++) printf("%d", res[i]);
    printf("\n");
    return 0;
}

B1077 互评成绩计算 (20 分)

#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++) {
        int a[n] = {0}, size = 0, t;
        for (int j = 0; j < n; j++) {
            scanf("%d", &t);
            if (t >= 0 && t <= m) a[size++] = t; //筛选出合法分数(包括老师的)
        }
        sort(a + 1, a + size);
        double sum = 0.0;
        for (int k = 2; k < size - 1; k++) sum += a[k]; //排除一个最小和一个最大值
        sum /= ((size - 1 - 2) * 1.0); //取平均分
        printf("%d\n", (int)round((sum + a[0]) / 2)); 
    }
    return 0;
}

B1078 字符串压缩与解压 (20 分)

字符串压缩就是把由相同字符组成的一个连续的片段用这个字符和片段中含有这个字符的个数来表示。例如ccccc就用5c来表示。如果字符没有重复,就原样输出。解压方法就是反过来,把形如5c这样的表示恢复为ccccc
照着题意模拟就是了,这题简单。

#include <cstdio>
#include <cctype>
#include <cstring>

int main() {
	char c, s[1010];
	scanf("%c", &c); getchar();
	scanf("%[^\n]", s);
	if (c == 'C') {
		for (int i = 0; s[i]; ) {
			char t = s[i];
			int j = i + 1; //一个字符连续重复的次数 
			while (s[j] && s[j] == t) j++;
			if (j - i <= 1) printf("%c", t);
			else printf("%d%c", j - i, t);
			i = j; //跳过相同的字符 
		}
	}
	else {
		for (int i = 0; s[i]; ) {
			int j = i, sum = 0;
			while (s[j] && isdigit(s[j])) {
				sum = sum * 10 + (s[j] - '0');
				j++;
			} 
			char t = s[j];
			if (sum) for (int k = 0; k < sum; k++) printf("%c", t);
			else printf("%c", t);
			i = j + 1; //跳过输出的字符 
		}
	}
	return 0;
}

(大整数) B1079 延迟的回文数 (20 分)

这题还是大整数的模板,就是加了两个逆转数字为回文数和判断回文数的函数。要注意的是:必须判断原始的数字是否是回文数,不然就会有三个测试点答案错误。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
struct bign {
	int d[N], len;
	bign() {
		memset(d, 0, sizeof(d));
		len = 0;
	}
};

bign change(char s[]) {
    bign a;
	a.len = strlen(s);
	for (int i = 0; s[i]; i++) {
		a.d[i] = s[a.len - i - 1] - '0';
	} 
	while (a.len > 1 && a.d[a.len - 1] == 0) a.len--;
	return a;
}

bign add(bign a, bign b) {
	bign t;
	int carry = 0;
	for (int i = 0; i < a.len || i < b.len; i++) {
		int r = (carry + a.d[i] + b.d[i]);
	    t.d[t.len++] = r % 10;
		carry = r / 10;	
	}
	if (carry) t.d[t.len++] = carry;
	return t;
}

bool isPalin(bign a) {
	for (int left = 0, right = a.len - 1; left < right; left++, right--) {
		if (a.d[left] != a.d[right]) return false;
	}
	return true;
}
bign bePalin(bign a) {
	bign t = a; //逆转过程中, 不用处理后面的0变成前导0的情况
	reverse(t.d, t.d + t.len);
	return t;
}

bign print(bign a) {
	for (int i = a.len - 1; i >= 0; i--) {
		printf("%d", a.d[i]);
	}
}

int main() {
	char s[N];
	scanf("%s", s);
	bign a = change(s), b, c;
    if (isPalin(a)) { //原始的数字是回文数, 就不需要经过这个逆转-相加-再逆转-相加…的过程
        print(a); printf(" is a palindromic number.\n");
    } else {
        int flag = 1; 
        for (int i = 0; i < 10; i++) {
            b = bePalin(a); //b是a的逆转数
            c = add(a, b);  //c是它们的和
            print(a); printf(" + "); print(b); //打印过程
            printf(" = "); print(c); printf("\n");
            if (isPalin(c)) { //c在10步以内变成回文数
                print(c); printf(" is a palindromic number.\n");
                flag = 0; break;
            } else a = c; //把和c赋给原始的数字, 重复操作直到C在10步以内变成回文数
        } //原始的数字在10步以内没有变成回文数
        if (flag) printf("Not found in 10 iterations.\n"); 
    }
	
	return 0;
}

★★(map) B1080 MOOC期终成绩 (25 分)

注释写的很清楚了。这里使用了c_str()函数用printf输出字符串。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
using namespace std;
struct student {
    string name;
    int gp, gm, gf, g; //在线编程成绩 期中考试成绩 期末考试成绩 总评
};

bool cmp(student a, student b) { //输出顺序为按照总评分数递减; 若有并列,则按学号递增
    return a.g != b.g ? a.g > b.g : a.name < b.name; //string可以直接比较大小
}
map<string, int> idx; //记录学生姓名和序号的映射
int main() {
    int p, n, m, score, cnt = 1;
    scanf("%d%d%d", &p, &n, &m);
    vector<student> v, ans; //合为一张的成绩单 获得合格证书的学生名单
    
    string s; 
    for (int i = 0; i < p; i++) {
        cin >> s >> score;
        if (score >= 200) { //有的成绩不存在则表示为-1
            v.push_back(student{s, score, -1, -1, 0});
            idx[s] = cnt++;
        }
    }
    for (int i = 0; i < m; i++) {
        cin >> s >> score; //所有人必须要编程>=200分
        if (idx[s] != 0) v[idx[s] - 1].gm = score; //不然不记录后面的成绩
    }
    for (int i = 0; i < n; i++) {
        cin >> s >> score;
        if (idx[s] != 0) {
            int temp = idx[s] - 1;
            v[temp].gf = v[temp].g = score;  //总评分数(四舍五入精确到整数)
            if (v[temp].gm > v[temp].gf) v[temp].g = int(round(v[temp].gm * 0.4 + v[temp].gf * 0.6));
        }
    }
    for (int i = 0; i < v.size(); i++)  //总评获得不少于60分
        if (v[i].g >= 60) ans.push_back(v[i]); 
    sort(ans.begin(), ans.end(), cmp);
    for (int i = 0; i < ans.size(); i++)  //至少存在1个合格的学生
        printf("%s %d %d %d %d\n", ans[i].name.c_str(), ans[i].gp, ans[i].gm, ans[i].gf, ans[i].g);
    return 0;
}

★(字符总结) B1081 检查密码 (15 分)

部分正确 13,错了一个测试点,不知道错在哪里?
不过这里使用了一下<cctype>头文件,尽管这些函数自己也不是不能写,但是有现成的还是更加方便。这个头文件里面的函数都很好记忆,因为所有函数都遵循这一原型:字符测试函数:int isxxxx(int); 字符转换函数:int toxxxx(int)。接受整型参数,返回整型数据。常用的函数包括isalpha、isdigit、isalnum、isgraph、islower、isupper、tolower、toupper

#include <cstdio>
#include <cctype>
#include <cstring>
char password[100];
void judge(char s[]) {
    if (strlen(s) < 6) {
        printf("Your password is tai duan le.\n"); 
    } else {
        int sf = 0, zf = 0; //数字标识、字母标识
        for (int i = 0; s[i]; i++) {
            if (!isalnum(s[i]) && s[i] != '.') { //有不合法字符 
            	printf("Your password is tai luan le.\n");
				return; 
			}
			if (isalpha(s[i])) zf = 1;
			if (isdigit(s[i])) sf = 1;
        }
        if (zf && sf) printf("Your password is wan mei.\n"); //既有字母也有数字
		else if (zf && !sf) printf("Your password needs shu zi.\n"); //没有数字
		else if (!zf && sf) printf("Your password needs zi mu.\n"); //没有字母
    }
}

int main() {
    int n;
    scanf("%d", &n);
    while (n--) {
        scanf("%s", password);
        judge(password);
    }
    return 0;
}

修正了。题目的要求是每行给出一个用户设置的密码,为不超过80个字符的非空字符串,以回车结束,但是字符串里面可能会有空格(从测试数据看,一定有的),所以不能直接用scanf("%s") / cin。因此要用类似gets的手段输入一行。
然而gets废止了,可以使用fgets(小心前面输入整数留下来的换行符!)或者使用cin.getline(char数组, 数组大小) / getline(cin, string类型)。同样的,要用getline接收一行字符,也要getchar读取一下换行符,否则只有换行符会被读进getline中。

#include <cstdio>
#include <cctype>
#include <cstring>
void judge(char s[]) {
    if (strlen(s) < 6) {
        printf("Your password is tai duan le.\n"); 
    } else {
        int sf = 0, zf = 0; //数字标识、字母标识
        for (int i = 0; s[i]; i++) {
			if (isalpha(s[i])) zf = 1;
			else if (isdigit(s[i])) sf = 1;
			else if (s[i] != '.') { //有不合法字符 
            	printf("Your password is tai luan le.\n"); return; 
			}
        }
        if (zf && sf) printf("Your password is wan mei.\n"); //既有字母也有数字
		else if (zf && !sf) printf("Your password needs shu zi.\n"); //没有数字
		else if (!zf && sf) printf("Your password needs zi mu.\n"); //没有字母
    }
}
char password[100];
int main() {
    int n;
    scanf("%d", &n);
    getchar(); //小心前面输入整数留下来的换行符!
    while (n--) {
        fgets(password, 100, stdin);
        int len = strlen(password);
        if (password[len - 1] == '\n') password[len - 1] = '\0';
        judge(password);
    }
    return 0;
}

B1082 射击比赛 (20 分)

冠军到靶心的距离最短

#include <cstdio>
#include <cmath>
struct cord {
    int id, x, y; 
    double distance;
} dis[10010];

double dist(int x, int y) { return sqrt(1.0 * (x * x + y * y)); }
int main() {
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d%d%d", &dis[i].id, &dis[i].x, &dis[i].y);
        dis[i].distance = dist(dis[i].x, dis[i].y);
    }
    int min = 0, max = 0; //冠军和菜鸟的序号
    for (int i = 0; i < n; i++) {
        if (dis[i].distance > dis[max].distance) max = i;
        if (dis[i].distance < dis[min].distance) min = i;
    }
    printf("%04d %04d\n", dis[min].id, dis[max].id);
    return 0;
}

B1083 是否存在相等的差 (20 分)

2 <= N <= 10000,差值最大不会超过10000。

#include <cstdio>
int hash[20010] = {0};
int main() {
    int n;
    scanf("%d", &n);
    int t, diff;
    for (int i = 1; i <= n; i++) {
        scanf("%d", &t);
        diff = i >= t ? i - t : t - i;
        hash[diff]++;
    }
    for (int i = 10000; i >= 0; i--) {
        if (hash[i] > 1) printf("%d %d\n", i, hash[i]);
    }
    return 0;
}

★ B1084 外观数列 (20 分)

外观数列是指具有以下特点的整数序列:

d, d1, d111, d113, d11231, d112213111, ...

它从不等于 1 的数字 d 开始,序列的第 n+1 项是对第 n 项的描述。比如第 2 项表示第 1 项有 1 个 d,所以就是 d1;第 2 项是 1 个 d(对应 d1)和 1 个 1(对应 11),所以第 3 项就是 d111。又比如第 4 项是 d113,其描述就是 1 个 d,2 个 1,1 个 3,所以下一项就是 d11231。当然这个定义对 d = 1 也成立。本题要求你推算任意给定数字d的外观数列的第N项

  • 输入格式:输入第一行给出 [0,9] 范围内的一个整数 d、以及一个正整数 N(≤ 40),用空格分隔。
  • 输出格式:在一行中给出数字 d 的外观数列的第 N 项。
  • 输入样例:
    1 8
    
  • 输出样例:
    1123123111
    

B1078 字符串压缩与解压 (20 分)差不多,能够在一个字符串中把由相同字符组成的一个连续的片段用这个字符和片段中含有这个字符的个数来表示,这个题就很简单了。大概自己动手手算模拟一下就行了。顺便说一句,题目中给出的不是d=1的情况,因此手动计算的时候可能会有点疑惑。
不过答案字符数组要开大一点,不然最后一个测试点会段错误

#include <cstdio>
#include <cstring>
const int maxn = 200000;
char ans[maxn], changed[maxn];
int len = 0;
int main() {
    int n, d;
    scanf("%d%d", &d, &n);
    if (n == 1) printf("%d", d);
    else if (n == 2) printf("%d1", d);
    else {
    	changed[0] = d + '0'; changed[1] = '1'; changed[2] = '\0';
	    for (int i = 3; i <= n; i++) { //从第三项开始 
	        for (int j = 0; changed[j]; ) {
				char t = changed[j]; int k = 0;
				while (changed[j + k] && changed[j + k] == t) k++;
				ans[len++] = changed[j]; 
				ans[len++] = k + '0'; //k代表一个字符重复的次数 
				j += k;
			} 
			ans[len] = '\0';
			if (i < n) {
			   strcpy(changed, ans); len = 0; 
            } 
	    }
		printf("%s", ans);    	
	}
	return 0;
}

★★(unordered_map) B1085 PAT单位排行 (25 分)

每次 PAT 考试结束后,考试中心都会发布一个考生单位排行榜。本题就请你实现这个功能。

  • 输入格式:输入第一行给出一个正整数 N(≤10​5​​),即考生人数。随后 N 行,每行按下列格式给出一个考生的信息:
    准考证号 得分 学校
    
    其中准考证号是由 6 个字符组成的字符串,其首字母表示考试的级别:B代表乙级,A代表甲级,T代表顶级;得分是 [0, 100] 区间内的整数;学校是由不超过 6 个英文字母组成的单位码(大小写无关)。注意:题目保证每个考生的准考证号是不同的。
  • 输出格式:首先在一行中输出单位个数。随后按以下格式非降序输出单位的排行榜:
    排名 学校 加权总分 考生人数
    
    其中排名是该单位的排名(从 1 开始);学校是全部按小写字母输出的单位码;加权总分定义为乙级总分/1.5 + 甲级总分 + 顶级总分*1.5的整数部分;考生人数是该属于单位的考生的总人数
    学校首先按加权总分排行。如有并列,则应对应相同的排名,并按考生人数升序输出。如果仍然并列,则按单位码的字典序输出。
  • 输入样例:
    10
    A57908 85 Au
    B57908 54 LanX
    A37487 60 au
    T28374 67 CMU
    T32486 24 hypu
    A66734 92 cmu
    B76378 71 AU
    A47780 45 lanx
    A72809 100 pku
    A03274 45 hypu
    
  • 输出样例:
    5
    1 cmu 192 2
    1 au 192 3
    3 pku 100 1
    4 hypu 81 2
    4 lanx 81 2
    

两个unordered_map,因为这个题需要映射也需要排序,但map的排序不是我们需要的排序,map也不允许使用sort函数,只好把它们作为统计数据的中间结构,而且为了加快速度,我们要使用unordered_map,不然会有测试点通不过。其中,一个cnt用来存储某学校名称对应的参赛人数,另一个sum计算某学校名称对应的总加权成绩。每次学校名称string school都要转化为全小写。

然后,将两个map中所有学校的信息都保存在vector ans中,类型为node,node中包括学校姓名、加权总分(此时对最后的总和取整数部分)、参赛人数。之后使用自定义的cmp函数对ans数组排序。

对于排名(同样的成绩有同样的排名,但是每个排名都会占据一个排位)的处理:设立rank表示排名,初始化为1,如果i > 0并且i指向的成绩大于前面的成绩,则rank等于数组下标+1(即等于排位),否则rank不变。

在这道题中使用了B1095的一些经验。如使用unordered_map;for (auto it : cnt) ans.push_back(node{it.first, (int)sum[it.first], cnt[it.first]});;排序时传递引用参数;复杂情况下用vector替代数组……

另外,map和vector都可以用operator[]进行访问,map是用[]中的数据作为key进行查询,而vector是用[]中的数作为下标进行访问。如果在用operator[]进行访问的时候出现了越界情况(即map没有这个键值对,或vector的大小小于下标数值),比如下面使用两个map,在没有这个key的情况下就进行访问并改变了value,会发生什么状况?(原文链接:https://blog.csdn.net/qq_34228570/article/details/79905443)

struct node{ int a{5}; };
int main() {
    map<string, node> m1;
    cout<< m1["s"].a <<endl;
    map<string, int> m2;
    cout<< m2["s"] <<endl;
    vector<node> v1(3); //需要指定vector大小,否则不能在没有push_back的情况用下标访问 
    cout<< v1[0].a <<endl;
    vector<int> v2(3);
    cout<< v2[0];
}

结果:

5
0
5
0

由上面示例程序可看出map和vector(需要指定vector大小)在越界情况都会给出此类型的默认值,如果是基本类型,则返回零值;如果是struct或class,如果里面的变量定义了默认值,则返回定义的默认值,如果没有,返回零值。

#include <cstdio>
#include <cctype>
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <algorithm>
using namespace std;
struct node {
	string school;
	int tws, ns; //加权总分 考生人数
};
bool cmp(const node &a, const node &b) { 
	if (a.tws != b.tws) return a.tws > b.tws; //首先按加权总分排行
	else if (a.ns != b.ns) return a.ns < b.ns; //按考生人数升序输出
	else return a.school < b.school; //按单位码的字典序输出
}
int main() {
	int n; scanf("%d", &n); //map本身不允许外部排序 
	unordered_map<string, int> cnt; //存储某学校名称对应的参赛人数
	unordered_map<string, double> sum; //存储某学校名称对应的总分(非整数部分)
	//只有一个学校得到了全部分数, 再取整数部分 
	for (int i = 0; i < n; i++) {
		string id, school; 
		cin >> id; 
		double score; scanf("%lf", &score);
	    cin >> school; //学校名称string要转化为全小写
	    for (int j = 0; j < school.length(); j++) 
	    	school[j] = tolower(school[j]);
		if (id[0] == 'B') score /= 1.5;
		else if (id[0] == 'T') score *= 1.5;
		sum[school] += score; //有默认初值
		cnt[school]++;  
	}
	vector<node> ans;
	for (auto it : cnt) ans.push_back(node{it.first, (int)sum[it.first], cnt[it.first]});
	sort(ans.begin(), ans.end(), cmp);
	printf("%d\n", (int)ans.size());
	int rank = 1; 
	for (int i = 0; i < ans.size(); i++) {
		if (i > 0 && ans[i].tws != ans[i - 1].tws) rank = i + 1;
		printf("%d ", rank);
		cout << ans[i].school;
		printf(" %d %d\n", ans[i].tws, ans[i].ns);
	}
	return 0;
}


B1086 就不告诉你 (15 分)

本题就要求你,对任何一对给定的正整数,倒着输出它们的乘积。

  • 输入格式:输入在第一行给出两个不超过1000的正整数A和B,其间以空格分隔。
  • 输出格式:在一行中倒着输出A和B的乘积。
  • 输入样例:
    5 7
    
  • 输出样例:
    53
    

这一题没讲清楚,但是从测试数据来看,要排除倒排后的前导0。如203 100,应该要输出302,中间的0不用去掉。

#include <cstdio>

int main() {
    int a, b, c;
    scanf("%d%d", &a, &b);
    c = a * b;
    int nums[10], size = 0, flag = 1;
    do {
    	while (c % 10 == 0 && flag) c /= 10; //排除倒排后的前导0 
    	flag = 0;
        nums[size++] = c % 10;
        c /= 10;
    } while (c > 0);     
    
    for (int i = 0; i < size; i++) {
        printf("%d", nums[i]);
    }
    printf("\n");
    return 0;
}

B1087 有多少不同的值 (20 分)

不同的值,简单整数映射。

#include <cstdio>
int hashtable[20000] = {0};

int main() {
    int n, ans = 0;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        int t = i / 2 + i / 3 + i / 5;
        if (hashtable[t] == 0) {
            hashtable[t] = 1;
            ans++;
        }
    }
    printf("%d\n", ans);
    return 0;
}

B1088 三人行 (20 分)

丙不一定是int值,可能是4.5这样的数字。所以要用double存储丙。i、j、k分别代表甲乙丙,i从99遍历到10找到符合题意的那个数字即可。没注意到这一点,就会有两个测试点出错。

#include <iostream>
#include <cmath>
using namespace std;
int m, x, y;
void print(double t) {
    if (m == t) printf(" Ping");
    else if (m < t) printf(" Cong");
    else printf(" Gai");
}
int main() {
    scanf("%d %d %d", &m, &x, &y);
    for (int i = 99; i >= 10; i--) {
        int j = i % 10 * 10 + i / 10;
        double k = abs(j - i) * 1.0 / x;
        if (j == k * y) {
            cout << i;
            print(i); print(j); print(k);
            return 0;
        }
    }
    cout << "No Solution";
    return 0;
}

★★ B1089 狼人杀-简单版 (20 分)

本题是这个问题的升级版:已知 N 名玩家中有2人扮演狼人角色,有 2 人说的不是实话,有狼人撒谎但并不是所有狼人都在撒谎。要求你找出扮演狼人角色的是哪几号玩家?

  • 输入格式:
    输入在第一行中给出一个正整数 N(5≤N≤100)。随后 N 行,第 i 行给出第 i 号玩家说的话(1≤i≤N),即一个玩家编号,用正号表示好人,负号表示狼人。
  • 输出格式:
    如果有解,在一行中按递增顺序输出 2 个狼人的编号,其间以空格分隔,行首尾不得有多余空格。如果解不唯一,则输出最小序列解 —— 即对于两个序列 A=a[1],…,a[M] 和 B=b[1],…,b[M],若存在 0≤k<M 使得 a[i]=b[i] (i≤k),且 a[k+1]<b[k+1],则称序列 A 小于序列 B。若无解则输出 No Solution
  • 输入样例 1:
    5
    -2
    +3
    -4
    +5
    +4
    
  • 输出样例 1:
    1 4
    
  • 输入样例 2:
    6
    +6
    +3
    +1
    -5
    -2
    +4
    
  • 输出样例 2(解不唯一):
    1 5
    
  • 输入样例 3:
    5
    -2
    -3
    -4
    -5
    -1
    
  • 输出样例 3:
    No Solution
    

从题目中可以得到以下几个事实:

  1. 狼人只有两个,一个说谎,一个没说谎;
  2. 说谎者只有两个,一个是狼人,一个是好人。

因为题目要求的是谁是狼人(而不是谁说了谎),所以假设枚举的这两个人都是狼人,如果可以推出一组解完美符合以上两个事实(从条件1推出条件2),就可以打印并退出了,没有就No Solution。

如果假设这里枚举的两个人都是说谎者(更适合求谁说谎的题目),那么本题对狼人的判断条件会复杂一点,还要找出说谎者中谁是狼人。不过也差不多。

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

int main() {
	int n;
	scanf("%d", &n);
	vector<int> v(n + 1);
	for (int i = 1; i <= n; i++) scanf("%d", &v[i]);
	
	for (int i = 1; i <= n; i++) {
		for (int j = i + 1; j <= n; j++) {
			vector<int> a(n + 1, 1); //表示对应的人是狼人-1还是好人1 
		    a[i] = a[j] = -1; //枚举的这两个是狼人 
		    vector<int> lie;  //看看谁说谎, 先假设v数组所有人说的都是对的 
		    //然后对比v数组和a数组, 某人说别人是好人, 别人在这里恰好是好人, 就没说谎
			//不然, 说别人是狼人而实际是好人, 说别人是好人而实际是狼人, 就说谎了   
			for (int k = 1; k <= n; k++) {  
				if (v[k] * a[abs(v[k])] < 0) { //说得话与对应的人不一致 
					lie.push_back(k);
				} 
			} //说谎的人只有两个, 一个是狼人一个是好人, 就找到了第一组解 
			if (lie.size() == 2 && a[lie[0]] + a[lie[1]] == 0) {
				cout << i << " " << j;
				return 0;  //直接退出 
			}
		}
	}
	cout << "No Solution";
	return 0; 
}

★(multimap) B1090 危险品装箱 (25 分)

集装箱运输货物时,我们必须特别小心,不能把不相容的货物装在一只箱子里。比如氧化剂绝对不能跟易燃液体同箱,否则很容易造成爆炸。
本题给定一张不相容物品的清单,需要你检查每一张集装箱货品清单,判断它们是否能装在同一只箱子里。

  • 输入格式:输入第一行给出两个正整数:N (≤10​4​​) 是成对的不相容物品的对数;M (≤100) 是集装箱货品清单的单数。
    随后数据分两大块给出。第一块有 N 行,每行给出一对不相容的物品。第二块有 M 行,每行给出一箱货物的清单,格式如下:
    K G[1] G[2] ... G[K]
    
    其中 K (≤1000) 是物品件数,G[i] 是物品的编号。简单起见,每件物品用一个 5 位数的编号代表。两个数字之间用空格分隔。
  • 输出格式:
    对每箱货物清单,判断是否可以安全运输。如果没有不相容物品,则在一行中输出 Yes,否则输出 No。
  • 输入样例:
    6 3
    20001 20002
    20003 20004
    20005 20006
    20003 20001
    20005 20004
    20004 20006
    4 00001 20004 00002 20003
    5 98823 20002 20003 20006 10010
    3 12345 67890 23333
    
  • 输出样例:
    No
    Yes
    Yes
    

这里的每件货物可能和多件货物不相容,因此我用了multimap,每个键对应多个值。它不支持[]操作。在multimap中,同一个键关联的元素必然相邻存放。基于这个事实,就可以一一遍历某个键对应的值。

  1. 使用find和count函数。count函数求出某个键出现的次数,find函数返回一个迭代器,指向第一个拥有正在查找的键的实例。
  2. 使用lower_bound(key)和upper_bound(key)
    lower_bound(key)返回一个迭代器,指向键不小于k的第一个元素;
    upper_bound(key)返回一个迭代器,指向键不大于k的第一个元素。
  3. 使用equat_range(key)
    返回一个迭代器的pair对象,first成员等价于lower_bound(key),second成员等价于upper_bound(key)。

我用的是第一种方法。

#include <cstdio>
#include <map>
#include <set>
using namespace std;
typedef multimap<int, int>::iterator mp_its;
typedef multimap<int, int>::size_type sz_type; //multimap数量类型
typedef set<int>::iterator st_its;
int main() {
    int n, m, t1, t2; //成对的不相容物品的对数; 集装箱货品清单的单数
    scanf("%d%d", &n, &m);
	multimap<int, int> mp; 
    while (n--) {
        scanf("%d%d", &t1, &t2); //一个键对应多个值
        mp.insert(make_pair(t1, t2));
        mp.insert(make_pair(t2, t1));
    }
    
    while (m--) {
        int k, flag = 1; 
        scanf("%d", &k);
        set<int> check; 
        for (int i = 0; i < k; i++) {
            scanf("%d", &t1);
            check.insert(t1);
        }    
        for (st_its st = check.begin(); st != check.end(); st++) {
        	sz_type t = mp.count(*st); //返回一个键对应的值个数
            if (t != 0) {              //存在不相容的货物
	            mp_its map_it = mp.find(*st);  
				for (sz_type i = 0; i != t; i++, map_it++) { //遍历这个键拥有的货物值 
					st_its s = check.find(map_it->second);
					if (s != check.end()) {
						printf("No\n"); 
						flag = 0; break;
					}
				}
            }
            if (flag == 0) break;
        }
        if (flag) printf("Yes\n"); 
    }
    return 0;
}


B1091 N-自守数 (15 分)

如果某个数K的平方乘以N以后,结果的末尾几位数等于K,那么就称这个数为“N-自守数”。例如3×92​2=25392,而25392的末尾两位正好是92,所以92是一个3-自守数。本题就请你编写程序判断一个给定的数字是否关于某个N是N-自守数

  • 输入格式:输入在第一行中给出正整数M(≤20),随后一行给出M个待检测的、不超过1000的正整数。
  • 输出格式:对每个需要检测的数字,如果它是N-自守数就在一行中输出最小的N和NK​2​的值,以一个空格隔开;否则输出No。注意题目保证N<10
  • 输入样例:
    3
    92 5 233
    
  • 输出样例:
    3 25392
    1 25
    No
    

部分正确 13:测试点二答案错误。

#include <cstdio>

int main() {
    int N, t;
    scanf("%d", &N);
    while (N--) {
        scanf("%d", &t);
        int t2 = t * t, flag = 0;
        for (int i = 1; i < 10; i++) {
            int temp = i * t2, low = 0, product = 1;
            while (temp / 10) { //temp还有最后一位(高位)时退出
                low += (temp % 10) * product;  // 逐渐检查低位
                if (low == t) {
                    printf("%d %d\n", i, i * t2);
                    flag = 1;
                    break;
                }
                product *= 10;
                temp /= 10;
            }
            if (flag) break;
        }
        if (!flag) printf("No\n");
    }
    return 0;
}

发现一个条件写错了,从类似的题目复制过来的,见鬼了,另一个题目倒是没错。这里说的是末尾几位,其实也可以包括首位,如1,1的平方再乘以1为1,因此1是1-守形数。

#include <cstdio>

int main() {
    int N, t;
    scanf("%d", &N);
    while (N--) {
        scanf("%d", &t);
        int t2 = t * t, flag = 0;
        for (int i = 0; i < 10; i++) {
            int temp = i * t2, low = 0, product = 1;
            while (temp != 0) { //temp为0时退出
                low += (temp % 10) * product;  // 逐渐检查低位
                if (low == t) {
                    printf("%d %d\n", i, i * t2);
                    flag = 1;
                    break;
                }
                product *= 10;
                temp /= 10;
            }
            if (flag) break;
        }
        if (!flag) printf("No\n");
    }
    return 0;
}

B1092 最好吃的月饼 (20 分)

#include <cstdio>

int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    int mooncake[n + 1] = {0};
    for (int i = 1; i <= m; i++) {
        int t;
        for (int j = 1; j <= n; j++) {
            scanf("%d", &t);
            mooncake[j] += t;
        }
    }
    int max = 1, cnt = 0;
    for (int i = 1; i <= n; i++) if (mooncake[i] > mooncake[max]) max = i;
    printf("%d\n", mooncake[max]);
    for (int j = 1; j <= n; j++) {
        if (mooncake[j] == mooncake[max]) {
            if (cnt == 0) {
                printf("%d", j); cnt++;
            } else printf(" %d", j);
        } 
    }
    printf("\n");
    return 0;
}

B1093 字符串A+B (20 分)

B1042 字符统计中也提到了这一点。为了避免缓冲区溢出,有些OJ禁用gets()函数,因此从终端读取一行输入时,应当用fgets()代替gets()函数。但是这也将带来一个问题(gets()的调用格式不同不用太关注)。

关键的问题在于:fgets()函数读取到它所遇到的第一个换行符的后面,或者读取比字符串的最大长度少一个的字符,然后fgets()函数向末尾添加一个空字符以构成一个字符串。也就是说,如果在达到字符最大数目之前读完一行,使用fgets会在字符串的‘\0’字符之前包含一个换行符

不同的是,gets会把一行字符串的换行符作为结束的标识,它会读入换行符并将之替换为结束符’\0’。本题中也是如此,不把换行符视作字符串的一部分,所以我们需要手动去除这个多余的换行符
/* C++版本 */

#include <cstdio>
#include <cstring>
const int maxn = 1000100;
char s[maxn];
bool hashtable[128] = {false};
int main() {
    for (int c = 1; c <= 2; c++) {
        fgets(s, maxn, stdin);
        int len = strlen(s); //去除多余的换行符
        if (s[len - 1] == '\n') s[len - 1] = '\0';
        for (int i = 0; s[i]; i++) {
            if (hashtable[s[i]] == false) {
                hashtable[s[i]] = true;
                printf("%c", s[i]);
            }
        }
    }
    return 0;
}

/* C版本 */

#include <stdio.h>
#include <string.h>
const int maxn = 1000100;
int main() {
    char s[maxn];
    int hashtable[128] = {0};
    for (int c = 1; c <= 2; c++) {
        gets(s);
        for (int i = 0; s[i]; i++) {
            if (hashtable[s[i]] == 0) {
                hashtable[s[i]] = 1;
                printf("%c", s[i]);
            }
        }
    }
    return 0;
}

★(atoi/stoi) B1094 谷歌的招聘 (20 分)

本题要求你编程解决一个更通用的问题:从任一给定的长度为 L 的数字中,找出最早出现的 K 位连续数字所组成的素数。

  • 输入格式:输入在第一行给出 2 个正整数,分别是 L(不超过1000 的正整数,为数字长度)和 K(小于10的正整数)。接下来一行给出一个长度为 L 的正整数 N。
  • 输出格式:在一行中输出 N 中最早出现的 K 位连续数字所组成的素数。如果这样的素数不存在,则输出 404。注意,原始数字中的前导零也计算在位数之内。例如在 200236 中找 4 位素数,0023 算是解;但第一位 2 不能被当成 0002 输出,因为在原始数字中不存在这个 2 的前导零。
  • 输入样例 1:
    20 5
    23654987725541023819
    
  • 输出样例 1:
    49877
    
  • 输入样例 2:
    10 3
    2468024680
    
  • 输出样例 2:
    404
    

题目大意:给出一个l长度的字符串,求出其中第一个k位的素数。
分析: 枚举每个k位的字符串,转换成整数,判断是否是素数。思路是这样的,但是实现有点问题。
部分正确 18:这里忘记输出题目中提到的原始数字0了。

#include <cstdio>
#include <cmath>
bool isPrime(long long n) {
    if (n <= 1) return false;
    int sqr = (int)sqrt(1.0 * n);
    for (int i = 2; i <= sqr; i++) {
        if (n % i == 0) return false;
    }
    return true;
}
int main() {
    //L<=1000; K<10; 形成的最大为9位数值, 不会溢出整型范围 
    int L, K, flag = 1; 
    scanf("%d%d", &L, &K); 
    char s[L + 10], len = 0;
    scanf("%s", s);
    for (int i = 0; s[i]; i++) { //遍历每一位后的K位数
        long long sum = s[i] - '0';
        for (int j = 1; j < K; j++)  //遍历K位
            sum = sum * 10 + s[i + j] - '0';
        if (isPrime(sum) == 1) {
            printf("%lld", sum);
            flag = 0; break;
        }
    }
    if (flag) printf("404");
    return 0;
}

正确代码(字符数组+手动转换数字):

#include <cstdio>
#include <cstring>
#include <cmath>
int isPrime(long long n) {
    if (n <= 1) return 0;
    int sqr = (int)sqrt(1.0 * n);
    for (int i = 2; i <= sqr; i++) {
        if (n % i == 0) return 0;
    }
    return 1;
}
int main() {
    //L<=1000; K<10; 形成的最大为9位数值, 不会溢出整型范围 
    int L, K; 
    scanf("%d%d", &L, &K); 
    char s[L + 10];
    scanf("%s", s);
    for (int i = 0; i <= L - K; i++) { //遍历每一位后的K位数
        char sum[L + 10]; sum[0] = s[i];
        int num = s[i] - '0';
        for (int j = 1; j < K; j++) { //遍历K位
            sum[j] = s[i + j];
            num = num * 10 + s[i + j] - '0';
        }
        sum[K] = '\0'; //切记
        if (isPrime(num) == 1) {
            printf("%s", sum);
            return 0;
        }
    }
    printf("404\n");
    return 0;
}

正确代码(字符数组+atoi函数):atoi(表示ascii to integer)是把字符数组const char *转换成整型数的一个函数,该函数要求被转换的字符串是按十进制数理解的。函数原型:int atoi (const char * str);

另外C/C++还提供的类似的标准库函数有:(1)long int atol ( const char * str );  (2)double atof (const char* str);,此外还有sscanf(),可以提供字符串到整数的转换。作用是将字符数组保存在整数中。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
int isPrime(long long n) {
    if (n <= 1) return 0;
    int sqr = (int)sqrt(1.0 * n);
    for (int i = 2; i <= sqr; i++) {
        if (n % i == 0) return 0;
    }
    return 1;
}
int main() {
    //L<=1000; K<10; 形成的最大为9位数值, 不会溢出整型范围 
    int L, K; 
    scanf("%d%d", &L, &K); 
    char s[L + 10];
    scanf("%s", s);
    for (int i = 0; i <= L - K; i++) { //遍历每一位后的K位数
        char sum[L + 10]; sum[0] = s[i];
        for (int j = 1; j < K; j++) { //遍历K位
            sum[j] = s[i + j];
        }
        sum[K] = '\0';
        if (isPrime(atoi(sum)) == 1) {
            printf("%s", sum);
            return 0;
        }
    }
    printf("404\n");
    return 0;
}

正确代码(字符串+stoi函数):包含在string头文件中。 在Dev C++编译器中使用stoi这样的函数,可能会出现‘stoi’ was not declared in this scope,即使头文件<string>已包含。需要改变编译器设置。

与atoi不同的是:
①atoi()的参数是const char*,因此一个字符串str,我们必须调用 c_str()的方法把string转换成const char*类型,而stoi()的参数是const string*,可以直接使用字符串;
stoi()会做范围检查默认范围是在int的范围内的,如果超出范围的话则会runtime error!
而atoi()不会做范围检查,如果超出范围的话,超出上界,则输出上界,超出下界,则输出下界

#include <iostream>
#include <string>
using namespace std;
bool isPrime(int n) {
    if (n == 0 || n == 1) return false;
    for (int i = 2; i * i <= n; i++)
        if (n % i == 0) return false;
    return true;
}
int main() {
    int l, k;
    string s;
    cin >> l >> k >> s;
    for (int i = 0; i <= l - k; i++) {
        string t = s.substr(i, k);
        int num = stoi(t);
        if (isPrime(num)) {
            cout << t;
            return 0;
        }
    }
    cout << "404\n";
    return 0;
}

★★(unordered_map) B1095 解码PAT准考证 (25 分)

题目大意:给出一组学生的准考证号和成绩,准考证号包含了等级(乙甲顶),考场号,日期,和个人编号信息,并有三种查询方式
查询一:给出考试等级,找出该等级的考生,按照成绩降序,准考证升序排序;
查询二:给出考场号,统计该考场的考生数量和总得分;
查询三:给出考试日期,查询改日期下所有考场的考试人数,按照人数降序,考场号升序排序。

  • 分析:先把所有考生的准考证和分数记录下来。
  1. 按照等级查询,枚举选取匹配的学生,然后排序即可。
  2. 按照考场查询,枚举选取匹配的学生,然后计数、求和。
  3. 按日期查询每个考场人数,用unordered_map存储,最后排序汇总。

注意:

  1. 第三个用map存储会超时,用unordered_map就不会超时。事实上unordered_map是C++11标准中增加的,用来处理只映射而不按key排序(这才是我们用得最多的)的情况,速度比map快得多。PAT支持这个标准,因此可以使用。和multimap一样,我们要掌握。
  2. 排序传参建议用引用传参,这样更快。
  3. set也有unordered_set,有multiset,同样的情况。
  4. for (auto it : m) ans.push_back(student{it.first, it.second});
#include <cstdio>
#include <iostream>
#include <vector> 
#include <string>
#include <unordered_map>
#include <algorithm> 
using namespace std;
struct student {
	string t;
	int value;
};

bool cmp(struct student &a, struct student &b) {
	if (a.value != b.value) return a.value > b.value; //非升序排列成绩 
	else return a.t < b.t; //分数并列的考生按其准考证号的字典序递增排列 
}

int main() {
	int n, m, num; 
	string s;
	scanf("%d%d", &n, &m);
	vector<student> v(n); 
	for (int i = 0; i < n; i++) {
		cin >> v[i].t >> v[i].value;
	}
	for (int i = 1; i <= m; i++) { //该项要求的编号从1开始
		cin >> num >> s;
		printf("Case %d: %d %s\n", i, num, s.c_str());
		vector<student> ans;
		int cnt = 0, sum = 0;
		switch (num) {
			case 1:
				for (int j = 0; j < n; j++) {
					if (v[j].t[0] == s[0]) ans.push_back(v[j]);
				} 
				break;
			case 2:
				for (int j = 0; j < n; j++) {
					if (v[j].t.substr(1, 3) == s) {//匹配考场编号101到999
		                cnt++;  //累计某考场的考生人数
	                    sum += v[j].value;  //累计某考场的总分
					} 
				}
				if (cnt != 0) printf("%d %d\n", cnt, sum);
				break; 
			case 3:	 //查询该日期下所有考场的考试人数,按照人数降序,考场号升序排序
		        unordered_map<string, int> m; //记录考场号和考试人数
		        for (int j = 0; j < n; j++) { //对应考场号作为键方便映射 
		        	if (v[j].t.substr(4, 6) == s) m[v[j].t.substr(1, 3)]++; 
				} 
		        for (auto it : m) ans.push_back(student{it.first, it.second}); 
		        break; 
		}
		sort(ans.begin(), ans.end(), cmp);
		for (int j = 0; j < ans.size(); j++) printf("%s %d\n", ans[j].t.c_str(), ans[j].value);
		if (((num == 1 || num == 3) && ans.size() == 0) || (num == 2 && cnt == 0)) printf("NA\n");
	}
	return 0;
}
已标记关键词 清除标记
相关推荐