【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=5N = 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章