PAT甲級 散列題_C++題解

散列

PAT (Advanced Level) Practice 散列題ios

目錄

  • 《算法筆記》 重點摘要
  • 1002 A+B for Polynomials (25)
  • 1009 Product of Polynomials (25)
  • 1084 Broken Keyboard (20)
  • 1092 To Buy or Not to Buy (20)
  • 1116 Come on! Let's C (20)
  • 1121 Damn Single (25)

《算法筆記》 4.2 散列 重點摘要

1. 散列

將元素經過一個函數轉換爲整數,使得該整數能夠儘可能惟一地表明這個元素。算法

2. 散列函數

通常直接用STL中的 map 或 unordered_map,除非必須模擬這些方法或對算法效率要求較高,不然不須要本身實現解決衝突的方法數組

(1) 直接定址法
  • 恆等變換:將 key(輸入的數) 做爲數組下標 ⭐——最多見最實用
  • 線性變換:H(key) = a * key + b
(2) 平方取中法 —— 不多用

取 key 平方的中間若干位做爲 hash 值函數

(3) 除留餘數法

H(key) = key % mod (表長Tsize >= mod,不然會越界)spa

  • 開放定址法
    • 線性探查法:H(key), H(key)+1, ...
    • 平方探查法:H(key), H(key)+1^2, H(key)-1^2, H(key)+2^2, H(key)-2^2, ...
      • if (H(key) + k^2 > Tsize) H(key)+k^2 % Tsize
      • if (H(key) - k^2 < 0) ((H(key) - k^2) % Tsize + Tsize) % Tsize (可只正向探查避免此種麻煩)
  • 鏈地址法

3. 字符串 hash

將一個字符串S映射爲一個整數,使得整數能夠儘量惟一地表明字符串S.net

(1) 字符串由大寫字母 A-Z 組成

將大寫字母 A-Z 視爲 0-25,即對應到了26進制中,再轉換爲10進制。轉換爲的整數最大爲26^len-1,len爲字符串長度code

int hashFunc(char S[], int len){
    int id = 0;
    for (int i = 0; i < len; i++){
        id = id * 26 + (S[i] - 'A');
    }
    return id;
}
(2) 字符串由大寫字母 A-Z 和小寫字母 a-z 組成

52進制轉10進制問題排序

(3) 字符串由大寫字母 A-Z,小寫字母 a-z 和 數字 1-9 組成
  • (1) 62進制轉10進制問題
  • (2) 若保證字符串末尾是肯定個數的數字,可將前面英文字母部分轉換爲整數後將數字拼接上去
int hashFunc(char S[], int len){
    int id = 0;
    for (int i = 0; i < len - 1; i++){
        id = id * 26 + (S[i] - 'A');
    }
    id = id * 10 + (S[len-1] - '0');
    return id;
}

1002 A+B for Polynomials (25)

#include<iostream>
using namespace std;
int main()
{
    int k, n, sum = 0;
    double a, p[1001] = {0};
    scanf("%d", &k);
    for (int i = 0; i < k; i++){
        scanf("%d%lf", &n, &a);
        p[n] += a;
    }
    scanf("%d", &k);
    for (int i = 0; i < k; i++){
        scanf("%d%lf", &n, &a);
        p[n] += a;
    }
    for (int i = 0; i < 1001; i++) if (p[i]) sum++;
    printf("%d", sum);
    for (int i = 1000; i >= 0; i--) if (p[i]) printf(" %d %.1f", i, p[i]);
    return 0;
}

簡化代碼(參考柳婼小姐姐的代碼

  • 和多項式可直接在輸入時計算,沒必要分別存儲兩個多項式再相加。
  • 輸出格式控制可將空格放在內容前面輸出,這樣不論檢查到哪裏輸出尾部都不會有多餘空格,不須要把不爲 0 的單獨放到 map 裏集中輸出
  • 簡化前代碼以下
#include<iostream>
#include<map>
using namespace std;
int main()
{
    int k, n, sum = 0;
    double a, p1[1002] = {0}, p2[1002] = {0}, p[1002] = {0};
    scanf("%d", &k);
    for (int i = 0; i < k; i++){
        scanf("%d%lf", &n, &a);
        p1[n] = a;
    }
    scanf("%d", &k);
    for (int i = 0; i < k; i++){
        scanf("%d%lf", &n, &a);
        p2[n] = a;
    }
    for (int i = 0; i < 1002; i++) p[i] = p1[i] + p2[i];
    map<int,double,greater<int>> p_map;
    for (int i = 0; i < 1002; i++){
        if (p[i]){
            sum++;
            p_map[i] = p[i];
        }
    }
    printf("%d", sum);
    for (auto it : p_map) printf(" %d %.1f", it.first, it.second);
    return 0;
}

1009 Product of Polynomials (25)

題目思路

  • 先用兩個數組分別將第一個多項式的指數和係數都接收進來,接收第二個時能夠邊接收邊計算邊保存結果。
  • 結果記錄在一個大數組中,結果數組的索引即爲結果多項式的指數,內容爲此冪次的係數
  • 結果多項式有多少項須要遍歷一次結果數組記錄係數不爲零的項數
  • 注意:由於相乘後指數可能最大爲2000,因此ans數組最大要開到2001
#include<cstdio>
double result[2001] = {0};
int main()
{
    int k1, k2, exp2;
    double coe2;
    scanf("%d", &k1);
    int *exp1 = new int[k1];
    double *coe1 = new double[k1];
    for (int i = 0; i < k1; i++) scanf("%d%lf", exp1+i, coe1+i);
    scanf("%d", &k2);
    for (int i = 0; i < k2; i++){
        scanf("%d%lf", &exp2, &coe2);
        for (int j = 0; j < k1; j++)
            result[exp1[j] + exp2] += coe1[j] * coe2;
    }
    int k = 0;
    for (int i = 0; i < 2001; i++)
        if (result[i] != 0) k++;
    printf("%d",k);
    for (int i = 2000; i >= 0; i--)
        if (result[i] != 0)
            printf(" %d %.1f", i, result[i]);
}

1084 Broken Keyboard (20)

參考思路(參考柳婼小姐姐的代碼

  • 遍歷原字符串,在打印的字符串中未出現的就是壞掉的
  • 只能輸出一次,用集合記錄輸出過的。
#include<iostream>
#include<set>
using namespace std;
int main()
{
    string a, b;
    cin >> a >> b;
    set<char> printed;
    for (int i = 0; i < a.length(); i++){
        if (b.find(a[i]) == b.npos && printed.find(toupper(a[i])) == printed.end()){
            printf("%c", toupper(a[i]));
            printed.insert(toupper(a[i]));
        }
    }
    return 0;
}

簡化代碼

  • string 也能夠用 find 查找字符,且因爲每次插入前都檢查是否已經出現,不會重複,能夠用 string 替代 set 存儲壞掉的字符
#include<iostream>
using namespace std;
int main()
{
    string a, b, broken;
    cin >> a >> b;
    for (int i = 0; i < a.length(); i++)
        if (b.find(a[i]) == b.npos && broken.find(toupper(a[i])) == broken.npos)
            broken += toupper(a[i]);
    cout << broken;
    return 0;
}

題目思路

  • 字母出現過即意味着沒有壞掉,若以前在 broken 集合中要 erase 掉
  • 遇到兩字符串不匹配要檢查是否在出現過的字母裏,若不在就加到 broken 集合中
  • 輸出時要按順序且只能輸出一次,用集合記錄輸出過的,遍歷字符串,若在 broken 中且沒有輸出過就輸出。
#include<iostream>
#include<set>
using namespace std;
int main()
{
    string a, b;
    cin >> a >> b;
    int p = 0;
    set<char> broken, okay, printed;
    for (int i = 0; i < a.length(); i++){
        if (a[i] == b[p]){
            p++;
            okay.insert(toupper(a[i]));
            if (broken.find(toupper(a[i])) != broken.end()) broken.erase(toupper(a[i]));
        }
        else if (okay.find(toupper(a[i])) == okay.end()) broken.insert(toupper(a[i]));
    }
    for (int i = 0; i < a.length(); i++){
        if (broken.find(toupper(a[i])) != broken.end() && printed.find(toupper(a[i])) == printed.end()){
            printed.insert(toupper(a[i]));
            printf("%c", toupper(a[i]));
        }
    }
    return 0;
}

1092 To Buy or Not to Buy (20)

#include<iostream>
#include<map>
using namespace std;
int main()
{
    map<char,int> shop;
    string s, e;
    cin >> s >> e;
    for (int i = 0; i < s.length(); i++) shop[s[i]]++;
    int miss = 0;
    for (int i = 0; i < e.length(); i++){
        if (shop.find(e[i]) != shop.end() && shop[e[i]] > 0) shop[e[i]]--;
        else miss++;
    }
    if (!miss) printf("Yes %d", s.length()-e.length());
    else printf("No %d", miss);
    return 0;
}
  • 若是能夠買,說明店主的珠子覆蓋了想買的,則店主珠子數 - 想買珠子數 即爲多餘的
  • 簡化前代碼以下:去除了想買的珠子後剩下的店主珠子和,沒必要要再算一遍
#include<iostream>
#include<map>
using namespace std;
int main()
{
    map<char,int> shop;
    string s, e;
    cin >> s >> e;
    for (int i = 0; i < s.length(); i++) shop[s[i]]++;
    int extra = 0, miss = 0;
    for (int i = 0; i < e.length(); i++){
        if (shop.find(e[i]) != shop.end() && shop[e[i]] > 0) shop[e[i]]--;
        else miss++;
    }
    if (!miss){
        for (auto it = shop.begin(); it != shop.end(); it++) extra += it->second;
        printf("Yes %d", extra);
    }
    else printf("No %d", miss);
    return 0;
}

1116 Come on! Let's C (20)

題目思路

  • 按順序接收 ranklist 輸入,將 id 和名次放入 map,因爲只須要查詢 id 對應的名次不須要排序,用 unordered_map 更快
  • 每次接收新的查詢 id 先到 map 中查找是否在 ranklist 中,再到 set 中查找是否已經查詢過。
  • 若未查詢過,放入 set,再按照對應名次輸出獎品。
  • 注意:不在 ranklist 輸出優先級更高,若一個不在 ranklist 中的 id 以前被檢查過,應當輸出 Are you kidding? 而非 Checked,因此先查 map 後查 set
#include<unordered_map>
#include<iostream>
#include<set>
using namespace std;
bool isPrime(int n){
    if (n <= 1) return false;
    for (int i = 2; i * i <= n; i++)
        if (n % i == 0) return false;
    return true;
}
int main()
{
    int n, k, id;
    scanf("%d", &n);
    unordered_map<int,int> rank;
    for (int i = 0; i < n; i++){
        scanf("%d", &id);
        rank.insert({id,i+1});
    }
    set<int> checked;
    scanf("%d", &k);
    for (int i = 0; i < k; i++){
        scanf("%d", &id);
        if (rank.find(id) == rank.end()) printf("%04d: Are you kidding?\n", id);
        else {
            if (checked.find(id) != checked.end()) printf("%04d: Checked\n", id);
            else {
                checked.insert(id);
                if (rank[id] == 1) printf("%04d: Mystery Award\n", id);
                else if (isPrime(rank[id])) printf("%04d: Minion\n", id);
                else printf("%04d: Chocolate\n", id);
            }
        }
    }
    return 0;
}
  • map.insert({key,value}) map 插入值可直接用 {} 括起來,省去寫 pair 的麻煩

1121 Damn Single (25)

題目思路

  • 開大數組記錄每一個人的伴侶編號
  • 將受到邀請的客人所有放到一個 set 中,以便於後面查找一個客人的伴侶是否在場
  • 遍歷 guests 集合,couple 數組對應值爲 -1(無伴侶) 或 伴侶不在場的壓入 vector
  • 有可能在場沒有單身狗,要先檢查 lonely 是否爲空
  • lonely 不爲空則對 lonely 排序後集中輸出
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
int couple[100000];
int main()
{
    int n, m, a, b;
    scanf("%d", &n);
    fill(couple,couple+100000,-1);
    for (int i = 0; i < n; i++){
        scanf("%d%d", &a, &b);
        couple[a] = b;
        couple[b] = a;
    }
    scanf("%d", &m);
    set<int> guests;
    for (int i = 0; i < m; i++){
        scanf("%d", &a);
        guests.insert(a);
    }
    vector<int> lonely;
    for (auto it = guests.begin(); it != guests.end(); it++)
        if (couple[*it] < 0 || guests.find(couple[*it]) == guests.end()) lonely.push_back(*it);
    if (lonely.empty()) printf("0\n");
    else{
        sort(lonely.begin(),lonely.end());
        printf("%d\n%05d",lonely.size(), lonely[0]);
        for (int i = 1; i < lonely.size(); i++) printf(" %05d",lonely[i]);
    }
    return 0;
}
  • memset(數組名, 值, sizeof(數組名); 在<cstring>中,建議只用來賦 0 或 -1
  • fill(first, last, value ); 在<algorithm>中,可將 first-last 範圍內填充爲 value 值
  • set.find (value); 在集合中查找 value 值,找到返回 iterator,不然返回 set::end.
相關文章
相關標籤/搜索