大概刷了 50 題左右 GPE Helper 上面比較常出現的題目,根據題目類型分類做一些筆記,方便之後複習。

本篇主題大多與數學相關。

質數表
Problem UVA
24461 Sum of Consecutive Prime Numbers 1210
23571 Smith Numbers 10042
約瑟夫問題
Problem UVA
21944 Power Crisis 151
10607 Joseph’s Cousin 10015
座標系
Problem UVA
2009-24 Unique lines
10551 Bee Maja 10182
運算式
Problem UVA
2008-37 Prefix expression evaluation
23671 Camel trading 10700

質數表

Sum of Consecutive Prime Numbers

UVA 1210

題目大意

根據題目輸入的數字,計算其能夠寫成幾種連續質數的和,ex: 53 = 5 + 7 + 11 + 13 + 17 同時 53 也是一個質數,故需要輸出 2。

解題方法

先建立一個儲存質數的 vector,不斷篩選出質數並放入其中,之後直接用暴力法測試是否能夠湊出題目要求的數字。

程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <bits/stdc++.h>

using namespace std;

vector<int> prime_table(10001, 1), prime;
int n;

int main(){
for(int i = 2; i <= 10000; i++){
if(prime_table[i] == 1){ //質數表
for(int j = i + i; j <= 10000; j += i) prime_table[j] = 0;
prime.push_back(i);
}
}
while(cin >> n && n != 0){
int c = 0;
for(int i = 0; prime[i] <= n && i < prime.size(); i++){
int s = 0;
for(int j = i; s < n && j < prime.size(); j++){
s += prime[j]; //不斷累加
}
if(s == n) c++;
}
cout << c << endl;
}
return 0;
}

Smith Numbers

UVA 10042

題目大意

Smith Numbers 的定義為原數字的各位數字和與其質因數分解的每個數字的各位數字和相同,ex: 4937775 = 3 · 5 · 5 · 65837 且兩邊的各位數字和相同。輸入一個數字,求出大於該數字的最小 Smith Numbers

解題方法

建立一個質數表,方便之後的質因數分解,從題目給出的數字開始增加,並且判斷是否為 Smith Numbers,每次質因數分解結束時可能會有三種情況需要分開討論:

  • 剩下 1,代表已經全部處理完畢。
  • 剩下 自己,代表這個數字是質數 (沒有小於等於自己開根號的質數)。
  • 剩下 不是自己的質數,把剩下數字的各位數字相加。

之後比較兩邊數字是否相等,就能夠做出判斷。

注意事項
  • 質數不是 Smith Numbers
程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <bits/stdc++.h>

using namespace std;

vector<int> prime;
vector<bool> p(100000, true);

void primeTable(){ //建立質數表
for(int i = 2; i < 100000; i++){
if(p[i]){
prime.push_back(i);
for(int j = i + i; j < 100000; j += i){
p[j] = false;
}
}
}
return;
}

int s(int x){ //計算各位數字和
int c = 0;
while(x != 0){
c += (x % 10);
x /= 10;
}
return c;
}

bool check(int x){ //檢查是否為 Smith Numbers
if(x < 100000 && p[x]) return false; //該數為質數
int c1 = s(x), c2 = 0, tmp = x;
for(int i = 0; i < prime.size(); i++){
if(x == 1) break;
if(x % prime[i] == 0){
int t = s(prime[i]);
while(x % prime[i] == 0){
c2 += t;
x /= prime[i];
}
}
}
if(x == tmp) return false; //該數為質數
if(x != 1) c2 += s(x);
if(c1 == c2) return true;
return false;
}

int n, m;

int main(){
primeTable();
cin >> n;
while(n--){
cin >> m;
for(int i = m + 1; i <= INT_MAX; i++){
if(check(i)){
cout << i << endl;
break;
}
}
}
return 0;
}

約瑟夫問題

Power Crisis

UVA 151

題目大意

基本上就是 約瑟夫問題,每次輸入一個大於 13 的人數 n,求出數字 m 做為每次跳過的人數,使得第 13 個人可以最後存活。

解題方法

遍歷 1 ~ n 的數字,使用約瑟夫問題的公式,計算最後存活的人並判斷是否正確。

注意事項
  • 約瑟夫問題公式要注意。
程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <bits/stdc++.h>

using namespace std;

int n;

bool check(int n, int x){
int k = 0;
for(int i = 1; i < n; i++){
k = (k + x) % i;
}
return (k == 11);
}

int main(){
while(cin >> n && n != 0){
for(int i = 1; i <= n; i++){
if(check(n, i)){
cout << i << endl;
break;
}
}
}
return 0;
}

Joseph’s Cousin

UVA 10015

題目大意

約瑟夫問題 的變體,每次輸入人數 n,並且第 i 次跳過的人數為第 i 個質數,ex:第一次跳 2 人,第二次跳 3 人,第三次跳 5 人…,求出最後剩下的人。

解題方法

把質數表建好,套上約瑟夫問題的公式,不過每次跳過的人數是質數表上對應的數字。

注意事項
  • 注意套用公式的時候質數要由大到小帶入。
程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <bits/stdc++.h>

using namespace std;

vector<bool> v(100000, true);
vector<int> p;

void prime(){
for(int i = 2; i < 50000; i++){
if(v[i]){
p.push_back(i);
if(p.size() == 3501) break;
for(int k = i + i; k <= 50000; k += i){
v[k] = false;
}
}
}
}

int f(int n){
int k = 0;
for(int i = 1; i <= n; i++){
k = (k + p[n - i]) % i; //由大到小
}
return k + 1;
}

int n;

int main(){
prime();
while(cin >> n && n != 0){
cout << f(n) << endl;
}
return 0;
}

座標系

Unique lines

題目大意

給定若干個平面上的點,求這幾個點可以產生多少不同的線。

解題方法

首先遍歷所有點,並儲存每條邊的斜率,以及經過的其中一個點,分別儲存兩點間在 xy 軸的變化量,之後遍歷所有儲存的邊,檢查是否有重複邊。

注意事項
  • 如果是儲存斜率的話,可能會有正無限與負無限判斷的問題,分別儲存 xy 軸的變化量可以避免這個問題。
程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <bits/stdc++.h>

using namespace std;

int n, t;
vector<vector<int>> v;

int main(){
cin >> n;
while(n--){
v.clear();
int ans = 0;
cin >> t;
int arr[t][2];
for(int i = 0; i < t; i++){ //遍歷所有點建立邊
cin >> arr[i][0] >> arr[i][1];
for(int j = 0; j < i; j++){
v.push_back({arr[i][0] - arr[j][0], arr[i][1] - arr[j][1], arr[i][0], arr[i][1]});
}
}
for(int i = 0; i < v.size(); i++){ //遍歷所有邊檢查重複
int b = 1;
for(int j = i + 1; j < v.size(); j++){
if(v[i][0] * v[j][1] != v[i][1] * v[j][0]) continue;
if(v[i][0] * (v[i][3] - v[j][3]) == v[i][1] *(v[i][2] - v[j][2])){
b = 0;
break;
}
}
if(b == 1) ans++;
}
cout << ans << endl;
}
return 0;
}

Bee Maja

UVA 10182

題目大意

在一個六邊形的座標系中,有兩種表達格子的方式 (Maja’s & Willi’s),每次輸入一個數字 (Willi’s),需要轉換成 Maja’s 座標表達方式輸出。

解題方法

觀察六邊形的右下那條邊,可以發現兩種表達方式都有規律,Maja’s 座標分別為 {0, 1}, {0, 1}, {0, 2}...Willi’s 座標分別為 1, 7, 19... 可以發現他們的差為 6 的整數倍,根據規律可以提前把轉換的表建立好。

程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <bits/stdc++.h>

using namespace std;

int n, dir[6][2] = {{0, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 0}, {1, -1}};
vector<pair<int, int>> v(200000);

int main(){
for(int i = 0; i <= 200; i++){
int p = 1 + i * (i + 1) * 3, x = i, y = 0;
v[p] = {x, y}; //p 點對應座標
for(int j = 0; j < 5; j++){
for(int k = 0; k < i; k++){
p--;
x += dir[j][0];
y += dir[j][1];
v[p] = {x, y};
}
}
for(int k = 0; k < i - 1; k++){
p--;
x += dir[5][0];
y += dir[5][1];
v[p] = {x, y};
}
}
while(cin >> n){
cout << v[n].first << " " << v[n].second << endl;
}
return 0;
}

運算式

Prefix expression evaluation

題目大意

根據輸入的前序表達式計算其結果,如果該表達式不合法則輸出 illegal

解題方法

將輸入的字串由後往前檢查,如果是數字則放入 stack 中,如果是符號則取出 stack 頂部的兩個數字做運算,運算完之後再放回 stack 中,最後剩下的數字就是計算的結果,如果在運算的過程有遇到 stack 為空的情況代表表達式有錯誤。

注意事項
  • 遍歷字串的時候是由後往前的,因此處理數字的時候要注意。
程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <bits/stdc++.h>

using namespace std;

string str;

int main(){
while(getline(cin, str) && str != "."){
stack<int> stk;
int n = 0, t = 1, b = 1;
for(int i = str.size() - 1; i >= 0; i--){ //由後往前
if(str[i] >= '0' && str[i] <= '9'){ //數字
n += t * (str[i] - '0');
t *= 10;
}else{
if(str[i] == ' '){ //空格
if(str[i + 1] >= '0' && str[i + 1] <= '9'){
stk.push(n);
t = 1;
n = 0;
}
continue;
}
if(stk.empty()){
b = 0;
break;
}
int n1 = stk.top();
stk.pop();
if(stk.empty()){
b = 0;
break;
}
int n2 = stk.top();
stk.pop();
int tmp;
if(str[i] == '+') tmp = n1 + n2;
else if(str[i] == '-') tmp = n1 - n2;
else if(str[i] == '*') tmp = n1 * n2;
else if(str[i] == '/') tmp = n1 / n2;
else if(str[i] == '%') tmp = n1 % n2;
stk.push(tmp);
}
}
if(b == 0) cout << "illegal" << endl;
else{
int ans = stk.top();
stk.pop();
if(!stk.empty()) cout << "illegal" << endl;
else cout << ans << endl;
}
}
return 0;
}

Camel trading

UVA 10700

題目大意

題目的輸入為一個式子,裡面只包含數字以及 *, + 兩個符號,可以隨意地在式子中加入括號,求出計算後所能得到的最大以及最小值。

解題方法

最大值一定是把所有有加號的數字先相加之後才相乘,最小值則是先處理相乘之後再相加,可以先將符號和數字分別儲存,後面再取出處理。

程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <bits/stdc++.h>

using namespace std;

long long maxn(vector<long long> num, vector<char> op){ //先加後乘
for(int i = 0; i < op.size(); i++){
if(op[i] == '+'){
num[i + 1] += num[i];
num[i] = 1;
}
}
for(int i = 1; i < num.size(); i++){
num[i] *= num[i - 1];
}
return num[num.size() - 1];
}

long long minn(vector<long long> num, vector<char> op){ //先乘後加
for(int i = 0; i < op.size(); i++){
if(op[i] == '*'){
num[i + 1] *= num[i];
num[i] = 0;
}
}
for(int i = 1; i < num.size(); i++){
num[i] += num[i - 1];
}
return num[num.size() - 1];
}

int n;
string str;

int main(){
cin >> n;
while(n--){
cin >> str;
vector<long long> num;
vector<char> op;
int tmp = 0;
//把數字和符號分開儲存
for(int i = 0; i < str.size(); i++){
if(str[i] == '+' || str[i] == '*'){
op.push_back(str[i]);
num.push_back(tmp);
tmp = 0;
}else tmp = tmp * 10 + (str[i] - '0');
}
num.push_back(tmp);
cout << "The maximum and minimum are " << maxn(num, op) << " and " << minn(num, op) << "." << endl;
}
return 0;
}