<div style="display: flex; justify-content: space-between;"> <a href="https://hackmd.io/@vientoligero/S1TVZchL6" style="margin-left: auto; color: grey; font-size: 14px;">下一頁 a017~</a> </div> # zerojudge基礎題庫題解 a001~a015 ## 傳送門 * [a001 哈囉](#a001) * [a002 簡單加法](#a002) * [a003 兩光法師占卜術](#a003) * [a004 文文的求婚](#a004) * [a005 Eva 的回家作業](#a005) * [a006 一元二次方程式](#a006) * [a009 解碼器](#a009) * [a010 因數分解](#a010) * [a013 羅馬數字](#a013) * [a015 矩陣的翻轉](#a015) <a id=a001></a> ## zerojudge-a001 哈囉 ### [題目](https://zerojudge.tw/ShowProblem?problemid=a001) :::info > 內容 學習所有程式語言的第一個練習題 請寫一個程式,可以讀入指定的字串,並且輸出指定的字串。 比如:輸入字串 "world", 則請輸出 "hello, world" >輸入說明 輸入總共一行,內含一組文字 >輸出說明 輸出題目指定的文字。 > 標籤 基本輸入輸出 ::: ### 範例輸入&輸出 :::info > 範例輸入 #1 world > 範例輸出 #1 hello, world >範例輸入 #2 C++ >範例輸出 #2 hello, C++ >範例輸入 #3 Taiwan >範例輸出 #3 hello, Taiwan ::: ### 題解 :::warning 讀入字串後, 加上"hello, "輸出 ::: ### Python > 程式碼 ```python= #處理輸入 s = input() #依題意輸出 print("hello,", s) ``` _註1: ```print()```輸出多個東西時(Eg: `print(1, 2, 3, 4, 5`), 每個東西之間會用空白(` `)隔開。_ _註2: 註1說的東西其實只是預設啦,有興趣的可以上網查查看。`print()`裡面可以放`, sep=""`來改變`print()`輸出多個東西時,要用甚麼分隔,預設就是空白`" "`)_ ### C > 程式碼 ```c= #include <stdio.h> int main(){ // 處理輸入 char s[100]; scanf("%s", s); // 依題意輸出 printf("hello, %s", s); return 0; } ``` ### C++ > 程式碼 ```cpp=1 //引入必要header #include <iostream> #include <string> //讓之後的程式不用加std:: using namespace std; int main(){ //處理輸入 string s; cin>>s; //依題意輸出 cout << "hello, " << s; return 0; } ``` ### Java > 程式碼 ```java=1 //導入處理輸入的Scanner import java.util.Scanner; public class Main { public static void main(String[] args) { //處理輸入 Scanner scanner = new Scanner(System.in); String s = scanner.nextLine(); //依題意輸出 System.out.println("hello, " + s); //關閉Scanner物件 scanner.close(); } } ``` ###### 感謝[M_SQRT](https://zerojudge.tw/UserStatistic?id=195452)勘誤。「對象」(Object)是對岸的說法,在台灣應翻譯為「物件」 <a id=a002></a> ## zerojudge-a002 簡易加法 ### [題目](https://zerojudge.tw/ShowProblem?problemid=a002) :::info > 內容 請寫一個程式,讀入兩個數字,並求出它們的和。 >輸入說明 每組輸入共一行,內含有兩個整數a, b,以空白隔開,a, b絕對值皆小於$10^6$。 >輸出說明 對於每組輸入,輸出該兩整數的和。 > 標籤 基本輸入輸出 ::: ### 範例輸入&輸出 :::info >範例輸入 #1 5 10 >範例輸出 #1 15 >範例輸入 #2 1 2 >範例輸出 #2 3 ::: ### 題解 :::warning 讀取兩個整數後,將其相加並輸出 ::: ### Python > 程式碼 ```python= #讀取輸入 a, b = map(int, input().split()) #輸出 print(a+b) ``` _註1: `.split()`能將字串依照特定字串分割(預設是空白`' '`),並且回傳一個list_ _註2: `map(函數, 序列)`可以將一序列中的每個元素做某個函式,並回傳一個map物件(迭代器),裡面放執行完的結果,通常外面會包一個`list()`把它轉成陣列。_ ### C > 程式碼 ```c= #include <stdio.h> int main(){ //處理輸入 int a, b; scanf("%d %d", &a, &b); //輸出答案 printf("%d", a+b); return 0; } ``` ### C++ > 程式碼 ```cpp=1 #include <iostream> using namespace std; int main(){ //處理輸入 int a, b; cin >> a >> b; //輸出答案 cout << a+b; return 0; } ``` ### Java > 程式碼 ```java=1 import java.util.Scanner; public class Main{ public static void main(String[] args){ //處理輸入 Scanner scanner = new Scanner(System.in); int a = scanner.nextInt(); int b = scanner.nextInt(); //輸出答案 System.out.println(a+b); scanner.close(); } } ``` <a id=a003></a> ## zerojudge-a003 兩光法師占卜術 ### [題目](https://zerojudge.tw/ShowProblem?problemid=a003) :::info > 內容 兩光法師時常替人占卜,由於他算得又快有便宜,因此生意源源不絕,時常大排長龍,他想算 得更快一點,因此找了你這位電腦高手幫他用電腦來加快算命的速度。 他的占卜規則很簡單,規則是這樣的,輸入一個日期,然後依照下面的公式: M=月 D=日 S=(M*2+D)%3 得到 S 的值,再依照 S 的值從 0 到 2 分別給與 普通、吉、大吉 等三種不同的運勢 >輸入說明 輸入資料共一行,包含兩個整數,分別為月份及日期 >輸出說明 運勢 > 標籤 if-else ::: ### 範例輸入&輸出 :::info >範例輸入 #1 1 1 >範例輸出 #1 普通 > 範例輸入 #2 1 2 >範例輸出 #2 吉 ::: ### 題解 :::warning 算出S後,用if-else判斷S的值並輸出對應答案 ::: ### Python > 程式碼 (if-else版本) ```python= #讀入資料 map, split用法請見a002 m, d = map(int, input().split()) #算出s的值 s = (m*2+d)%3 #判斷s是否為0, 1, or 2 ,條件成立時輸出對應答案 if s==0: print("普通") elif s==1: print("吉") else: #整數%3 只有0, 1, 2三種結果。 會跑到else的情況只有s==2,就不多加判斷了 print("大吉") ``` > 程式碼 (match-case版本) (欣賞一下就好) ```python=1 m, d = map(int, input().split()) s = (m*2 + d) % 3 match s: case 0: print("普通") case 1: print("吉") case 2: print("大吉") ``` _註1: Python從3.10版本才開始有```match-case```,用法類似C++,Java的```switch-case```。目前zerojudge不支援_ ### C >程式碼 (if-else 版) ``` c= #include <stdio.h> int main() { // 讀取輸入 int m, d; scanf("%d %d", &m, &d); // 算出s的數值 int s = (2 * m + d) % 3; // 判斷s的值並輸出對應答案 if (s == 0) { printf("普通\n"); } else if (s == 1) { printf("吉\n"); } else { // 整數%3只有0, 1, 2三種結果,會跑到else的情況只有s==2 printf("大吉\n"); } return 0; } ``` > 程式碼 (switch-case版) ```c= #include <stdio.h> int main() { // 讀取輸入 int m, d; scanf("%d %d", &m, &d); // 算出s的數值 int s = (2 * m + d) % 3; // 判斷s的值並輸出對應答案 switch (s) { case 0: printf("普通\n"); break; case 1: printf("吉\n"); break; case 2: printf("大吉\n"); break; } return 0; } ``` _註1: 每個case的內容結束完後都要加`break`來中止整個switch的區塊(除非要特殊運用case的機制),不然s==0(case 0成立)又沒有加上`break`時case 0 1 2的內容都會被執行,也就是會輸出`普通吉大吉`_ ### C++ > 程式碼 (if-else版) ```cpp=1 #include <iostream> using namespace std; int main(){ //讀取輸入 int m, d; cin >> m >> d; //算出s的數值 int s = (2*m+d)%3; //判斷s是否為0, 1, or 2 ,條件成立時輸出對應答案 if (s==0){ cout << "普通"; } else if(s==1){ cout << "吉"; } else{ //整數%3 只有0, 1, 2三種結果。 會跑到else的情況只有s==2,就不多加判斷了 cout << "大吉"; } } ``` > 程式碼 (switch-case版) ```cpp=1 #include <iostream> using namespace std; int main(){ //讀取輸入 int m, d; cin >> m >> d; //算出s的數值 int s = (2*m+d)%3; //判斷s是否為0, 1, or 2 ,條件成立時輸出對應答案 switch (s){ case 0: cout << "普通"; break; case 1: cout << "吉"; break; case 2: cout << "大吉"; break; } } ``` _註1: 每個case的內容結束完後都要加`break`來中止整個switch的區塊(除非要特殊運用case的機制),不然s==0(case 0成立)又沒有加上`break`時case 0 1 2的內容都會被執行,也就是會輸出`普通吉大吉`_ ### Java > 程式碼 (if-else版) ```java=1 import java.util.Scanner; public class Main{ public static void main(String[] args){ //處理輸入 Scanner scanner = new Scanner(System.in); int a = scanner.nextInt(); int b = scanner.nextInt(); //算出s的值 int s = (2*a+b)%3; //#判斷s是否為0, 1, or 2 ,條件成立時輸出對應答案 if (s==0){ System.out.println("普通"); } else if (s==1){ System.out.println("吉"); } else{ //整數%3 只有0, 1, 2三種結果。 會跑到else的情況只有s==2,就不多加判斷了 System.out.println("大吉"); } } } ``` > 程式碼 (switch-case版) ```java=1 import java.util.Scanner; public class Main{ public static void main(String[] args){ //處理輸入 Scanner scanner = new Scanner(System.in); int a = scanner.nextInt(); int b = scanner.nextInt(); //算出s的值 int s = (2*a+b)%3; //#判斷s是否為0, 1, or 2 ,條件成立時輸出對應答案 switch(s){ case 0: System.out.println("普通"); break; case 1: System.out.println("吉"); break; case 2: System.out.println("大吉"); break; } } } ``` *_註1: 每個case的內容結束完後都要加`break`來中止整個switch的區塊(除非要特殊運用case的機制),不然s==0(case 0成立)又沒有加上`break`時case 0 1 2的內容都會被執行,也就是會輸出`普通\n吉\n大吉`_ <a id=a004></a> ## zerojudge-a004 文文的求婚 ### [題目](https://zerojudge.tw/ShowProblem?problemid=a004) :::info > 內容 文文為即將出國的珊珊送行,由於珊珊不喜歡別人給文文的那個綽號,意思就是嘲笑文文不夠 聰明,但珊珊沒把握那個綽號是不是事實,所以珊珊決定考驗文文,於是告訴文文說,如果你能在 我回國之前回答我生日那年是不是閏年,則等她回國後就答應他的求婚。文文抓抓腦袋想不出來, 於是決定讓最擅長做運算的電腦來幫忙。 >輸入說明 輸入有若干行直到 EOF 結束,每行包含一個整數代表年份 >輸出說明 閏年 或 平年 > 標籤 EOF if-else ::: ### 範例輸入&輸出 :::info > 範例輸入 #1 1977 1980 > 範例輸出 #1 平年 閏年 ::: <a id=a004-1></a> ### 題解 :::warning 第一條路:判斷閏年 * 閏年條件: ((能被4整除)`and`(不能被100整除)) `or` (能被400整除) 第二條路:判斷平年 * 平年條件: ((不能被4整除) `or` (能被100整除)) `and` (不能被400整除) 再配上EOF輸入輸出處理即可 ::: ### EOF處理方法 :::danger 1. #### Python * way1: `while true` + `try-except` * **way2:** 用`sys.stdin`回傳標準輸入流的內容 2. #### C * **way1:** `while(scanf(~~) != EOF)` scanf如果讀到檔案尾部(`End-Of-File`)會回傳`EOF` * way2: `while(scanf(~~) == [a number])` scanf如果有讀到東西會回傳它讀取的數量 3. #### C++ * **way1:** ```while(cin>>inp)``` cin如果沒有抓到東西會回傳`false` * way2: 用 `cin.eof()`判斷還有沒有輸入 4. #### Java * **way1:** 用 `scanner.hasNext()`判斷還有沒有輸入 _註1:實際的運用在底下程式碼有_ _註2:粗體表示建議的_ ::: ### Python > 程式碼 (try-except版) ```python=1 while True: try: #讀取輸入 n = int(input()) #判斷並輸出對應答案 if ((n%4==0) and (n%100!=0)) or (n%400==0): print("閏年") else: print("平年") except EOFError: break #如果沒輸入了,就終止整個while迴圈 ``` _註1: `try-except-finally`用法: 運行程式進入`try-except-finally`時,程式會先執行`try`的內容,這時:_ * _**如果有出錯**:會進入`except`的內容 (except後可以加上各種錯誤,讓各種不同的錯誤發生時,跑進對應的excpet內;也可以不加,這樣各種不同的錯誤都會跑到同一個`expect`裡面)_ * _**如果沒有出錯**:會跑完`try`_ _`try` `except`跑完後,會進入`finally`內,最後結束整個`try-except-finally`_ ***簡單來說: 如果try內有發生錯誤就會跑 `try`(出錯前的程式部分)-`except`-`finally` 沒有出錯就會跑`try`-`finally`*** > 程式碼 (sys.stdin版) ```python=1 import sys #用for迴圈一一讀取標準輸入流的內容,讀完就跳出for迴圈,跳出程式 for inp in sys.stdin: inp = int(inp) #str-->int #判斷並輸出對應答案 if ((inp%4==0) and (inp%100!=0)) or (inp%400==0): print("閏年") else: print("平年") ``` _註1: sys.stdin 會回傳標準輸入流的內容。使用`for-loop`迭代時,通常後面會加上`inp = inp.strip()`來去掉不需要的換行符號和空白,只是在此程式中因為會把inp轉換成`int`來處理,就沒加了_ ### C > 程式碼 ```c= #include <stdio.h> int main() { int n; while (scanf("%d", &n) != EOF) { //處理EOF部分: 如果scanf沒有抓到東西 會回傳EOF // 這裡使用判斷閏年的 if-else if (((n % 4 == 0) && (n % 100 != 0)) || (n % 400 == 0)) { printf("閏年\n"); } else { printf("平年\n"); } } return 0; } ``` ### C++ > 程式碼 ( while(cin>>inp) 版) ```cpp=1 #include <iostream> using namespace std; int main(){ int n; while (cin>>n){ //處理EOF部分: 如果cin沒有抓到東西 會回傳false, while迴圈就會終止 //這裡使用判斷閏年的if-else if (((n%4==0) && (n%100!=0)) || (n%400==0)){ cout << "閏年" << endl; }else{ cout << "平年" << endl; } } return 0; } ``` > 程式碼 ( cin.eof() 版)(沒很建議,看看就好) ```cpp=1 #include <iostream> using namespace std; int main(){ int n; while (true){ cin >> n; //處理EOF部分:如果輸入流裡面沒資料了(若第11行的cin沒抓到東西)會break掉while迴圈 if (cin.eof()){ break; } if (((n%4==0) && (n%100!=0)) || (n%400==0)){ cout << "閏年" << endl; }else{ cout << "平年" << endl; } } return 0; } ``` _註1: `cin.eof()`:只要在最近一次cin達到輸入流尾端,`cin.eof()`就會返回`true`。消除方法是使用`cin.clear()`(把cin中的eofbit重置)_ _註2: 這個方法真的蠻爛的。很難應付許多情況(例如:明明變數是`int`結果使用者輸入`string`之類的)。 如果要用`while-true`+`cin`時,大多會多加上`cin.fail()`來判斷使用者輸入有沒有合乎預期 (當然,zerojudge的題目會說清楚輸入資料的形式)_ ### Java > 程式碼 ```java=1 import java.util.Scanner; public class JAVA{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n; //用scanner.hasNext()判斷輸入流中還有沒有資料 while (scanner.hasNext()){ n = scanner.nextInt(); //這裡是用判斷閏年的if-else if (((n%4==0) && (n%100!=0)) || (n%400==0)){ System.out.println("閏年"); }else{ System.out.println("平年"); } } } } ``` <a id=a005></a> ## zerojudge-a005 Eva 的回家作業 ### [題目](https://zerojudge.tw/ShowProblem?problemid=a005) :::info > 內容 Eva的家庭作業裏有很多數列填空練習。填空練習的要求是:已知數列的前四項,填出第五項。因 為已經知道這些數列只可能是等差或等比數列,她決定寫一個程式來完成這些練習。 >輸入說明 > 第一行是數列的數目t(0 <= t <= 20)。 以下每行均包含四個整數,表示數列的前四項。 約定數列的前五項均為不大於$10^5$的自然數,等比數列的比值也是自然數。 >輸出說明 對輸入的每個數列,輸出它的前五項。 > 標籤 (o_ _)o. o(´д`o).想不到 ::: ### 範例輸入&輸出 :::info >範例輸入 #1 2 1 2 3 4 1 2 4 8 >範例輸出 #1 1 2 3 4 5 1 2 4 8 16 ::: ### 題解 :::warning 由前三項判斷出是等差或是等比數列,再算出第五項 ::: ### Python >程式說明 :::success 針對每一筆資料,用if判斷出數列是否為等差($a_3-a_2==a_2-a_1$) 如果是等差: $a_5=a_4+d$ ($d$ : 公差 = $a_2-a_1$) 如果是等比: $a_5=a_4*r$ ($r$ : 公比 = $\dfrac{a_2}{a_1}$) 下面的程式碼因為python的list是從0開始的,所以第一項記做`p[0]`, 第二項記做`p[1]`... ::: > 程式碼 ```python= n = int(input()) for _ in range(n): p = list(map(int, input().split())) if (p[1]-p[0] == p[2]-p[1]): #True:=>是等差 ans = p[3] + (p[3]-p[2]) #p[4] = p[3]+d else: ans = p[3] * (p[3]//p[2]) #p[4] = p[3]*r print(p[0], p[1], p[2], p[3], ans) #亦可寫作print(*p, ans) ``` ### C >程式說明 :::success 針對每一筆資料,用if判斷出數列是否為等差($a_3-a_2==a_2-a_1$) 如果是等差: $a_5=a_4+d$ ($d$ : 公差 = $a_2-a_1$) 如果是等比: $a_5=a_4*r$ ($r$ : 公比 = $\dfrac{a_2}{a_1}$) 下面的程式碼因為c的array是從0開始的,所以第一項記做`p[0]`, 第二項記做`p[1]`... ::: > 程式碼 ```c=1 #include <stdio.h> int main() { int n; scanf("%d", &n); for (int i = 0; i < n; i++) { int p[5]; // 宣告一個大小為 5 的陣列 scanf("%d %d %d %d", &p[0], &p[1], &p[2], &p[3]); if (p[1]-p[0] == p[2]-p[1]) { // true ->是等差 p[4] = p[3] + (p[3]-p[2]); //p[4] = p[3] + d } else { // 等比 p[4] = p[3] * (p[3]/p[2]); //p[4] = p[3] * r } printf("%d %d %d %d %d\n", p[0], p[1], p[2], p[3], p[4]); } return 0; } ``` ### C++ > 程式說明 :::success 針對每一筆資料,用if判斷出數列是否為等差($a_3-a_2==a_2-a_1$) 如果是等差: $a_5=a_4+d$ ($d$ : 公差 = $a_2-a_1$) 如果是等比: $a_5=a_4*r$ ($r$ : 公比 = $\dfrac{a_2}{a_1}$) 下面的程式碼因為C++的array是從0開始的,所以第一項記做`p[0]`, 第二項記做`p[1]`... ::: > 程式碼 ```cpp=1 #include <iostream> using namespace std; int main(){ int n; cin>>n; for (int i=0; i<n; i++){ int p[5]; //宣告一個大小為5的陣列 cin >> p[0] >> p[1] >> p[2] >> p[3]; if (p[1]-p[0] == p[2]-p[1]){ //true:=>是等差 p[4] = p[3] + (p[3]-p[2]); //p[4] = p[3]+d } else{ p[4] = p[3] * (p[3]/p[2]); //p[4] = p[3]*r } cout << p[0] << ' ' << p[1] << ' ' << p[2] << ' ' << p[3] << ' ' << p[4] << endl; } return 0; } ``` ### Java >程式說明 :::success 針對每一筆資料,用if判斷出數列是否為等差($a_3-a_2==a_2-a_1$) 如果是等差: $a_5=a_4+d$ ($d$ : 公差 = $a_2-a_1$) 如果是等比: $a_5=a_4*r$ ($r$ : 公比 = $\dfrac{a_2}{a_1}$) 下面的程式碼因為Java的array是從0開始的,所以第一項記做`p[0]`, 第二項記做`p[1]`... ::: > 程式碼 ```java=1 import java.util.Scanner; public class JAVA{ public static void main(String[] args){ Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); for (int i=0; i<n; i++){ int[] p = new int[5]; // 宣告一個大小為 5 的陣列 //讀取測資 for (int j=0; j<4; j++){ p[j] = scanner.nextInt(); } if (p[1]-p[0] == p[2]-p[1]) { // true=>等差 p[4] = p[3] + (p[2]-p[1]); //p[4] = p[3]+d } else { p[4] = p[3] * (p[2]/p[1]); //p[4] = p[3]*r } //輸出答案 for (int j=0; j<5; j++) { System.out.print(p[j] + " "); } System.out.println(); //換行 } scanner.close(); } } ``` _註1:第24行用for迴圈輸出答案只是因為全部寫出來有點長~_ <a id=a006></a> ## zerojudge-a006 一元二次方程式 ### [題目](https://zerojudge.tw/ShowProblem?problemid=a006) :::info > 內容 求一元二次方程式 $ax^2+bx+c=0$ 的根 >輸入說明 每組輸入共一行,內含三個整數 a, b, c 以空白隔開。 >輸出說明 Two different roots x1=?? , x2=?? Two same roots x=?? No real root PS: 答案均為整數,若有兩個根則大者在前 > 標籤 算數學 ::: ### 範例輸入&輸出 :::info >範例輸入 #1 1 3 -10 >範例輸出 #1 Two different roots x1=2 , x2=-5 >範例輸入 #2 1 0 0 >範例輸出 #2 Two same roots x=0 >範例輸入 #3 1 1 1 >範例輸出 #3 No real root ::: ### 題解 :::warning 先算出判別式,再根據判別式的結果輸出答案 ::: ### 二元一次方程式公式 :::warning 一元二次方程式$ax^2+bx+c=0$的~ **公式解**: $x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$ **判別式**: $\Delta = b^2 - 4ac$ 如果判別式~ $\Delta > 0$ =>有兩個不同的實根 $\frac{-b + \sqrt{\Delta}}{2a}$ $\frac{-b - \sqrt{\Delta}}{2a}$ $\Delta = 0$ =>有兩個一樣的實根 $\frac{-b}{2a}$ $\Delta < 0$ =>沒有實根 ::: ### 各語言開根號方法 :::danger 1. #### Python * way1: `math.sqrt(a)` = a開2次方根的値 (= $\sqrt{a}$),a不能是負數 * **way2**: `a**(1/x)` = a開x次方根的値 (= $a^\frac{1}{x}$ = $\sqrt[x]{a}$) _註1: 此兩種方法的回傳型態都是float_ _註2: way1需要`import math`_ 2. #### C * way1: `sqrt(a)` = a開2次方根的値 (= $\sqrt{a}$),a不能是負數 * **way2**: `pow(a, 1.0/x)` = a開x次方根的値 (= $a^\frac{1}{x}$ = $\sqrt[x]{a}$) _註1: 此兩種方法的回傳型態都是double_ _註2: 此兩種方法都要`#include<math.h>`_ _註3: way2不能寫成`pow(a, 1/x)`,不然編譯器會把1/x當成int而砍掉小數點_ 3. #### C++ * way1: `std::sqrt(a)` = a開2次方根的値 (= $\sqrt{a}$),a不能是負數 * **way2**: `std::pow(a, 1.0/x)` = a開x次方根的値 (= $a^\frac{1}{x}$ = $\sqrt[x]{a}$) _註1: 此兩種方法的回傳型態都是double_ _註2: 此兩種方法都要`#include<cmath>`_ _註3: way2不能寫成`std::pow(a, 1/x)`,不然編譯器會把1/x當成int而砍掉小數點_ 4. #### Java * way1: `Math.sqrt(a)` = a開2次方根的値 (= $\sqrt{a}$),a不能是負數 * **way2**: `Math.pow(a, 1.0/x)` = a開x次方根的値 (= $a^\frac{1}{x}$ = $\sqrt[x]{a}$) _註1: 此兩種方法的回傳型態都是double_ _註2: way2不能寫成`Math.pow(a, 1/x)`,不然編譯器會把1/x當成int而砍掉小數點_ ::: ### Python > 程式碼 ```python= import math a, b, c = map(int, input().split()) delta = b**2 - 4*a*c if delta==0: root = -b//(2*a) print(f"Two same roots x={root}") elif delta>0: root1 = int( ( -b+math.sqrt(delta) )/(2*a) ) root2 = int( ( -b-math.sqrt(delta) )/(2*a) ) print(f"Two different roots x1={root1} , x2={root2}") else: #delta<0 print("No real root") ``` _註1: 第8行: 題目有保證答案是正整數且不能輸出小數點,所以用`//`而非`/`_ _註2: 第12, 13行: 理由同上,所以答案用`int()`包起來去除小數點_ ### C > 程式碼 ```c= #include <stdio.h> #include <math.h> int main() { int a, b, c; scanf("%d %d %d", &a, &b, &c); int delta = pow(b, 2) - 4 * a * c; if (delta == 0) { int root = -b / (2 * a); printf("Two same roots x=%d\n", root); } else if (delta > 0) { int root1 = (int)(-b + sqrt(delta)) / (2 * a); int root2 = (int)(-b - sqrt(delta)) / (2 * a); printf("Two different roots x1=%d , x2=%d\n", root1, root2); } else { //delta<0 printf("No real root\n"); } return 0; } ``` ### C++ > 程式碼 ```cpp=1 #include <iostream> #include <cmath> using namespace std; int main(){ int a, b, c; cin>>a>>b>>c; int delta = pow(b, 2) - 4*a*c; if (delta==0){ int root = -b / (2*a); cout << "Two same roots x=" << root; }else if(delta>0){ int root1 = ( -b+sqrt(delta) ) / (2*a); int root2 = ( -b-sqrt(delta) ) / (2*a); cout << "Two different roots x1=" << root1 << " , x2=" << root2; }else{ //delta<0 cout << "No real root"; } return 0; } ``` _註1: 第13, 17, 18行會拿int去存答案是因為題目有保證答案會是整數_ ### Java > 程式碼 ```java=1 import java.util.Scanner; public class Main{ public static void main(String[] args){ Scanner scanner = new Scanner(System.in); int a = scanner.nextInt(); int b = scanner.nextInt(); int c = scanner.nextInt(); int delta = (int) Math.pow(b, 2) - 4*a*c; if (delta==0){ int root = -b / (2*a); System.out.println("Two same roots x="+root); }else if(delta>0){ int root1 = (int)( -b+Math.sqrt(delta) ) / (2*a); int root2 = (int)( -b-Math.sqrt(delta) ) / (2*a); System.out.println("Two different roots x1="+root1+" , x2="+root2); }else{ //delta<0 System.out.println("No real root"); } scanner.close(); } } ``` _註1: 第14, 18, 19行會拿int去存答案是因為題目有保證答案會是整數_ _註2: 第18, 19行: 拿int去存還可以順便去掉小數點(4.0-->4)(題目規定的)_ _註3: **顯式轉換**(英文: Explicit Casting)與**隱式轉換**(英文: Implicit Casting)_ * _**顯示轉換**:把一個較大的資料型態給比較小的資料型態時,要用顯示轉換,即在待轉換的變數前面加上`( 想要轉成的資料型態 )`,要小心這樣可能導致精度降低或是溢位_ * _**隱式轉換**:把一個較小的資料型態給比較大的資料型態時,Java會自動轉換其資料型態,所以甚麼都不用做\_(:3 」∠ )\__ * _**資料型態大小**: `double`>`float`>`long`>`int`>`short`>`byte`_ _因此~第11, 18, 19行因為是`double`-->`int`,要使用顯示轉換,所以在待轉換的數值前面加上`(int)`_ <a id=a009></a> ## zerojudge-a009 解碼器 ### [題目](https://zerojudge.tw/ShowProblem?problemid=a009) :::info   在密碼學裡面有一種很簡單的加密方式,就是把明碼的每個字元加上某一個整數K而得到密碼的字元(明碼及密碼字元一定都在ASCII碼中可列印的範圍內)。例如若K=2,那麼apple經過加密後就變成crrng了。解密則是反過來做。這個問題是給你一個密碼字串,請你依照上述的解密方式輸出明碼。 至於在本任務中K到底是多少,請自行參照Sample Input及Sample Output推出來吧!相當簡單的。 >輸入說明 輸入共一行,每行含有1個字串,就是需要解密的明碼。 >輸出說明 對每一測試資料,請輸出解密後的密碼。 > 標籤 字元處理 ::: ### 範例輸入&輸出 :::info >範例輸入 #1 1JKJ'pz'{ol'{yhklthyr'vm'{ol'Jvu{yvs'Kh{h'Jvywvyh{pvu5 >範例輸出 #1 *CDC is the trademark of the Control Data Corporation. >範例輸入 #2 1PIT'pz'h'{yhklthyr'vm'{ol'Pu{lyuh{pvuhs'I|zpulzz'Thjopul'Jvywvyh{pvu5 > 範例輸出 #2 *IBM is a trademark of the International Business Machine Corporation. ::: ### 題解 :::warning 從範例輸入輸出照到規律,在寫程式時按照那個規律把每一筆輸入解密成原文 :::spoiler 規律 把密文的每一個字往前移7個就是原文了 ::: ### Python > 程式碼 ```python= s = input() decrypt = "" for char in s: trans = chr( ord(char)-7 ) #把每個字元抓出來轉換 decrypt = decrypt + trans #把轉換好的字元和已經轉換的字串組合起來 print(decrypt) ``` _註1: python字元與ASCII的轉換: `chr(n)`能回傳ASCLL値`n`所對應的字元(回傳`str`);`ord(s)`能回傳字元`s`所對應的ASCII値(回傳`int`) (ps: 字元在python中就是只有一個字的字串)_ ### C > 程式碼 ```c= #include <stdio.h> int main() { char s[1000]; scanf("%s", s); //遍歷s,把s中的每一個字元抓出來-7 for (int i = 0; s[i] != '\0'; i++) { s[i] -= 7; } printf("%s\n", s); return 0; } ``` _註1: C要改變字元的ASCII(往前往後多少個位置),直接把它加上一個整數即可 (char會被自動轉換成int)_ ### C++ > 程式碼 ```cpp=1 #include <iostream> #include <string> using namespace std; int main(){ string s; cin>>s; //遍歷s,把s中的每一個字元抓出來-7 for (char &c: s){ c-=7; } cout << s; } ``` _註1: C\+\+要改變字元的ASCII(往前往後多少個位置),直接把它加上一個整數即可 (char會被自動轉換成int)_ _註2: C++的**引用變數**: 在11行的`char &c`中,宣告的c是一個引用變數。他在遍歷s時,會和 s 中的字元共用一塊記憶體(也可以說兩個東西指向同一塊記憶體)。因此,當改變c的値的時候,s中的字元也會改變,這樣就可以直接修改 s 的內容了。_ ### Java > 程式碼 ```java=1 import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String s = scanner.nextLine(); char[] chars = s.toCharArray(); //String --> char[] //遍歷chars,把每一個元素都-7 for (int i = 0; i < chars.length; i++) { chars[i] = (char)(chars[i] - 7); } String decrypt = new String(chars);//char[] --> String System.out.println(decrypt); } } ``` _註1: Java要要改變字元的ASCII(往前往後多少個位置),直接把它加上一個整數即可 (char會變成int與int相加)。要注意`chars[i]-7`(上面第10行做舉例)是一個int,要存進char遍數時要顯示轉換`(char)~~~`_ <a id=a010></a> ## zerojudge-a010 因數分解 ### [題目](https://zerojudge.tw/ShowProblem?problemid=a010) :::info > 內容 各位在國小時都學過因數分解,都瞭解怎麼樣用紙筆計算出結果,現在由你來敎電腦做因數分解。 因數分解就是把一個數字,切分為數個質數的乘積,如 12=2^2 * 3 其中, 次方的符號以 ^ 來表示 >輸入說明 輸入共一行。每行包含一個整數,符合 大於1 且 小於等於 100000000 >輸出說明 針對每一行輸入整數輸出一個因數分解字串 > 標籤 ╮( ̄▽ ̄)╭ ::: ### 範例輸入&輸出 :::info >範例輸入 #1 20 >範例輸出 #1 2^2 * 5 >範例輸入 #2 17 >範例輸出 #2 17 >範例輸入 #3 999997 >範例輸出 #3 757 * 1321 ::: ### 題解 :::warning 這題要處理的東西大概可以分成兩個部分 #### Part1: 把數字分解 * 這個部分比較好想。可以使用最簡單(也最暴力)的方法——試除法(英文: Trial Division)。 具體的做法是從2開始往上除,除到$\sqrt{n}$為止。 (這樣好像沒有很清楚... 請參考[中文的維基百科](https://zh.wikipedia.org/zh-tw/%E8%AF%95%E9%99%A4%E6%B3%95)、[英文的維基百科(詳細一點)](https://en.wikipedia.org/wiki/Trial_division)和底下的流程圖~) ![流程圖本人](https://hackmd.io/_uploads/BJvGrFiV6.png) 正常試除法: 如上圖做法一直除到 $p >$$\sqrt{待分解的數}$ 之後。`所有除過p的數`和`除完還有剩的n`乘起來就是該數的質因數分解 但是~ 用上面的寫法實在是太麻煩了。考量到n最大只有到100000000。直接硬除除到`n==1`也是可以。雖然效率一定比較差,但是不差那一點啦d(\`・∀・)b #### Part2: 把答案印出 * 要印出答案的方法有很多~ 找到一個質因數就輸出一次,用**映射(mapping)**(Java和C++的map, Python的dict) 或是**陣列**儲存答案最後後一次輸出等等。就看個人喜好。 ::: ### Python >流程 (偷懶版) :::success #### Part1 分解: 按照上面題解的流程圖分解到目標數字剩下1為止(即分解完了),並把答案存入`result陣列`中 #### Part2 印出答案: 把裡面放tuple的`result陣列`中的元素一一取出,轉換成string後存入另一個陣列`ans` (轉換方法見程式碼)。最後把所有string連接起來印出 ::: > 程式說明 (偷懶版) ```python= n = int(input()) #part1: 把數字分解 target = n #待試除的數字 p = 2 result = [] while target!=1: times = 0 #紀錄除了幾次 while target%p ==0: #除到不能再除為止 times+=1 target //= p if times==0: #True=>沒除到=>非質因數 pass else: result.append( (p, times) ) p += 1 #part2-1: 處理答案 ans = [] for base, power in result: if power==1: ans.append(str(base)) #Eg: result中的(2, 1)到ans中會變成'2' else: ans.append(f"{base}^{power}") #Eg: result中的(3, 5)到ans中會變成'3^5' #part2-2: 輸出答案 print(" * ".join(ans)) ``` _註1: **`string.join(iterable object)`的功能**: `A.join(B)` 可以把`B陣列(或是任何可以迭代的物件)中的所有元素`用`A字串`接起來,最後回傳一個字串。 例如`" ".join(['a', 'b', 'c'])`會回傳字串`a b c`。_ _**注意:** 上面的B陣列中的元素必須是字串型態,否則直譯器會報錯`TypeError`_ _註2: 第34行的`print(" * ".join(ans))`也可以寫成`print(*ans, sep=" * ")`_ >程式說明 (比較有效率版) :::success 比較有效率版更改的地方是... 1. while的條件改成 $p^2 \leq n$ (意思就是 $p \leq \sqrt{n}$) (第8行) 2. while中加一段`if`判斷target是否早已被分解完 (第24, 25行) 3. while後加上else以確保n還有`(被分解剩的)target`這個質因數 (第29, 30行) ::: > 程式碼 (比較有效率版) ```python= n = int(input()) #part1: 把數字分解 target = n p = 2 result = [] while p*p <= n: times = 0 while target%p ==0: times+=1 target //= p if times==0: pass else: result.append( (p, times) ) if target==1: #已經分解完了,結束while-loop break p += 1 else: #while-loop沒有被break掉=>n還有一個質因數target result.append( (target, 1) ) #part2-1: 處理答案 ans = [] for base, power in result: if power==1: ans.append(str(base)) else: ans.append(f"{base}^{power}") #part2-2: 輸出答案 print(" * ".join(ans)) ``` _註1: 第8行的`while p*p <= n:`可以寫成`for p in range(2, int(n**0.5) + 1)` 然後把第26的`p+=1`跟第5行的`p = 2`去掉_ ### C > 程式說明(偷懶版) ::: success #### Part1 分解: 按照上面題解的流程圖分解到目標數字剩下1為止(即分解完了),並把答案存入`bases` `powers`兩個陣列中 #### Part2 印出答案: 把`bases` `powers`中的元素一一取出,並輸出答案 ::: >程式碼 (偷懶版) ```c= #include <stdio.h> int main() { int n; scanf("%d", &n); // Part 1: 分解數字 int target = n; //待試除的數字 int p = 2; int bases[30]; int powers[30]; int count = 0; //紀錄已找到幾個質因數 while (target != 1) { int times = 0; //紀錄除了多少次 while (target%p == 0) { //除到不能再除為止 times++; target /= p; } if (times > 0) { bases[count] = p; powers[count] = times; count++; } p++; } // Part 2: 輸出答案 for (int i=0; i<count; i++) { if (powers[i] == 1) { printf("%d", bases[i]); } else { printf("%d^%d", bases[i], powers[i]); } if (i != count-1) { //true: 不是最後一個 printf(" * "); } } return 0; } ``` > 程式說明(比較有效率版) :::success 比較有效率版更改的地方是... 1. while的條件改成 $p^2 \leq n$ (意思就是 $p \leq \sqrt{n}$) (第15行) 2. while中加一段`if`判斷target是否早已被分解完 (第29~31行) 3. while後加上if以確保n還有`(被分解剩的)target`這個質因數 (第36~40行) ::: > 程式碼(比較有效率版) ```c= #include <stdio.h> int main() { int n; scanf("%d", &n); // Part 1: 分解數字 int target = n; int p = 2; int bases[30]; int powers[30]; int count = 0; while (p*p <= n) { int times = 0; while (target%p == 0) { times++; target /= p; } if (times > 0) { bases[count] = p; powers[count] = times; count++; } if (target==1){ //true: target已經分解完了,結束while-loop break; } p++; } if (target != 1){ //true: n還有一個質因數: 分解剩的target bases[count] = target; powers[count] = 1; count++; } // Part 2: 輸出答案 for (int i=0; i<count; i++) { if (powers[i] == 1) { printf("%d", bases[i]); } else { printf("%d^%d", bases[i], powers[i]); } if (i != count-1) { printf(" * "); } } return 0; } ``` ### C++ > 程式說明 (偷懶版) :::success #### Part1 分解: 按照上面題解的流程圖分解到目標數字剩下1為止(即分解完了),並把答案(以pair型態)存入`result vector`中 #### Part2 印出答案: 把`result vector`中的pair一一取出,並輸出答案 ::: > 程式碼 (偷懶版) ```cpp=1 #include <iostream> #include <vector> #include <utility> //pair&make_pair #include <string> using namespace std; int main() { int n; cin >> n; // Part 1: 分解數字 int target = n; //待試除的數字 int p = 2; vector<pair<int, int>> result; while (target != 1) { int times = 0; //紀錄除了多少次 while (target % p == 0) { //除到不能再除為止 times+=1; target /= p; } if (times > 0) { result.push_back(make_pair(p, times)); } p+=1; } // Part 2-1: 處理答案 vector<string> ans; for (auto &data : result) { //auto也可寫成pair<int, int> int base = data.first; int power = data.second; if (power == 1) { ans.push_back(to_string(base)); //Eg: result中的{2, 1}到ans中會變成"2" } else { ans.push_back(to_string(base) + "^" + to_string(power)); //Eg: result中的{3, 5}到ans中會變成"3^5" } } //Part 2-2:輸出結果 for (int i = 0; i < ans.size(); i++) { cout << ans[i]; if (i < ans.size() - 1) { //還不是最後一個 cout << " * "; } } return 0; } ``` _註1:第27行的`result.push_back(make_pair(p, times));`可以寫成`result.push_back({p, times});`或是`result.emplace_back(p, times);`_ > 程式說明 (比較有效率版) :::success 比較有效率版更改的地方是... #### Part1: 1. while的條件改成 $p^2 \leq n$ (意思就是 $p \leq \sqrt{n}$) (第18行) 2. while中加一段`if`判斷target是否早已被分解完 (第31~33行) 3. while後加上if以確保n還有`(被分解剩的)target`這個質因數 (第38~40行) #### Part2: 4. 加了個sstream,會提高一點效率,程式碼也好看一點 ::: > 程式碼 (比較有效率版) ```cpp=1 #include <iostream> #include <vector> #include <utility> #include <string> #include <sstream> using namespace std; int main() { int n; cin >> n; // Part 1: 分解數字 int target = n; int p = 2; vector<pair<int, int>> result; while (p*p <= n) { int times = 0; while (target % p == 0) { times+=1; target /= p; } if (times > 0) { result.push_back(make_pair(p, times)); } if (target==1){ //true: target已經分解完了,結束while-loop break; } p+=1; } if (target != 1){ //true: n還有一個質因數: 分解剩的target result.push_back(make_pair(target, 1)); } // Part 2-1: 處理答案 stringstream ans; for (auto &data : result) { int base = data.first; int power = data.second; if (power == 1) { ans << base; } else { ans << base << "^" << power; } if (&data != &result.back()) { ans << " * "; } } //Part 2-2:輸出結果 cout << ans.str(); return 0; } ``` _註1: 第19行的`while (p*p <= n) {`也可以寫成`for(int p=2; p<=sqrt(n); p++) {`,(要`#include <cmath>`)然後第34行`p+=1;`跟第15行的`int p = 2;`去掉_ _註2: **sstream中的stringstream簡介**: stringstream可以把字串(string)變成字符串流(stringstream),讓他有和`cin`, `cout`一樣用`>>` `<<`寫入跟讀取的功能。詳細用法請詢問Google大神為您解惑。_ ### Java >程式說明 (偷懶版) :::success #### Part1 分解: 按照上面題解的流程圖分解到目標數字剩下1為止(即分解完了),並把答案分別存入`bases`, `powers`兩個ArrayList中 #### Part2 印出答案: 把`bases`, `powers`的元素一一取出,組合起來後直接輸出 _註1: 這個作法和上面C++,Python略有不同。因為Java的二維ArrsyList太難寫了,所以改用兩個ArrayList存答案(ゝ∀・)b_ ::: > 程式碼 (偷懶版) ```java=1 import java.util.Scanner; import java.util.ArrayList; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); //Part1: 分解數字 int target = n; int p = 2; ArrayList<Integer> bases = new ArrayList<>(); //儲存答案 ArrayList<Integer> powers = new ArrayList<>(); while (target != 1) { int times = 0; // 記錄除了多少次 while (target % p == 0) { // 除到不能再除為止 times++; target /= p; } if (times > 0) { bases.add(p); powers.add(times); } p++; } //Part2: 輸出答案 for (int i=0; i<bases.size(); i++){ if (powers.get(i) == 1) { //一次方不用印'^' System.out.print(bases.get(i)); }else { System.out.print(bases.get(i) + "^" + powers.get(i)); } if (i != bases.size()-1){ System.out.print(" * "); } } scanner.close(); } } ``` _註1: 來向各位分享一下敝人犯的智障錯誤: 第39行的 **`System.out.print(bases.get(i) + "^" + powers.get(i));`** 的意思是把`bases的第i個`和`^`和`powers的第i個`組合起來輸出。如果像我第一次寫寫成 **`System.out.print(bases.get(i) + '^' + powers.get(i));`** 會變成把兩個數字和`^的ASCII`加起來輸出。結果會不如預期喔_ >程式說明 (比較有效率版) ::: success 比較有效率版更改的地方是... #### Part1: 1. while的條件改成 $p^2 \leq n$ (意思就是 $p \leq \sqrt{n}$) (第20行) 2. while中加一段`if`判斷target是否早已被分解完 (第30-32行) 3. while後加上if以確保n還有`(被分解剩的)target`這個質因數 (第37-40行) #### Part2: 4. 加了StringBuilder,會提高一點效率 ::: >程式碼 (比要有效率版) ```java=1 import java.util.Scanner; import java.util.ArrayList; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); //Part1: 分解數字 int target = n; int p = 2; ArrayList<Integer> bases = new ArrayList<>(); ArrayList<Integer> powers = new ArrayList<>(); while (p*p <= n) { int times = 0; while (target % p == 0) { times++; target /= p; } if (times > 0) { bases.add(p); powers.add(times); } if (target==1){ //true=>已經分解完了 break; } p++; } if (target != 1){ //true: n還有一個質因數: 分解剩的target bases.add(target); powers.add(1); } //Part2: 輸出答案 StringBuilder strbld = new StringBuilder(); for (int i = 0; i < bases.size(); i++) { if (powers.get(i) == 1) { strbld.append(bases.get(i)); } else { strbld.append(bases.get(i)).append("^").append(powers.get(i)); } if (i != bases.size() - 1) { strbld.append(" * "); } } System.out.println(strbld.toString()); scanner.close(); } } ``` _註1: 第17行的`while (p*p <= n) {`可以改成 `for (int p=2; p<=Math.sqrt(n); p++) {` 然後把第34行`p++;`跟第13行`int p = 2;`去掉_ _註2: **java.lang.StringBuilder簡介:** StringBuilder能讓Java有一個可以改變內容的字串(本來的String不行)。裡面有`.append()`, `.insert()`, `.delete()`等等的函式。詳細用法請詢問Google大神。_ <a id=a013></a> ## zerojudge-a013 羅馬數字 ### [題目](https://zerojudge.tw/ShowProblem?problemid=a013) :::info > 內容 如果生活在數世紀之前的古羅馬,你應該用過 V 來表示五。V 和 5 這兩個符號都可以用來表示數目五。用來表示數目的符號稱作數字。而羅馬人用來表示數目的符號就是羅馬數字。 以下是七個基本的羅馬數字︰ <table style="width:100%; border-collapse: collapse;"> <tr style="background-color: #BECFD8;"> <th style="width: 50%; border: 1px solid #ddd; padding: 8px;">羅馬數字</th> <th style="width: 50%; border: 1px solid #ddd; padding: 8px;">數目</th> </tr> <tr style="background-color: #D6EAF4;"> <td style="width: 50%; border: 1px solid #ddd; padding: 8px;">I</td> <td style="width: 50%; border: 1px solid #ddd; padding: 8px;">1</td> </tr> <tr style="background-color: #D6EAF4;"> <td style="width: 50%; border: 1px solid #ddd; padding: 8px;">V</td> <td style="width: 50%; border: 1px solid #ddd; padding: 8px;">5</td> </tr> <tr style="background-color: #D6EAF4;"> <td style="width: 50%; border: 1px solid #ddd; padding: 8px;">X</td> <td style="width: 50%; border: 1px solid #ddd; padding: 8px;">10</td> </tr> <tr style="background-color: #D6EAF4;"> <td style="width: 50%; border: 1px solid #ddd; padding: 8px;">L</td> <td style="width: 50%; border: 1px solid #ddd; padding: 8px;">50</td> </tr> <tr style="background-color: #D6EAF4;"> <td style="width: 50%; border: 1px solid #ddd; padding: 8px;">C</td> <td style="width: 50%; border: 1px solid #ddd; padding: 8px;">100</td> </tr> <tr style="background-color: #D6EAF4;"> <td style="width: 50%; border: 1px solid #ddd; padding: 8px;">D</td> <td style="width: 50%; border: 1px solid #ddd; padding: 8px;">500</td> </tr> <tr style="background-color: #D6EAF4;"> <td style="width: 50%; border: 1px solid #ddd; padding: 8px;">M</td> <td style="width: 50%; border: 1px solid #ddd; padding: 8px;">1,000</td> </tr> </table> 所有其他的數目都是由這些數字組合而成。數目都是由左寫到右,通常值是等於組成的羅馬數字加起來。 例如十七可以表示為 <table style="width:100%; border-collapse: collapse; background-color: #D9EDF7;"> <tr style="background-color: #D9EDF7;"> <td style="border: 1px solid #D9EDF7; text-align: right; padding: 8px; background-color: #D9EDF7;">X</td> <td style="border: 1px solid #D9EDF7; text-align: center; padding: 8px; background-color: #D9EDF7;">+</td> <td style="border: 1px solid #D9EDF7; text-align: left; padding: 8px; background-color: #D9EDF7;">V</td> <td style="border: 1px solid #D9EDF7; text-align: center; padding: 8px; background-color: #D9EDF7;">+</td> <td style="border: 1px solid #D9EDF7; text-align: left; padding: 8px; background-color: #D9EDF7;">I</td> <td style="border: 1px solid #D9EDF7; text-align: center; padding: 8px; background-color: #D9EDF7;">+</td> <td style="border: 1px solid #D9EDF7; text-align: left; padding: 8px; background-color: #D9EDF7;">I</td> <td style="border: 1px solid #D9EDF7; text-align: center; padding: 8px; background-color: #D9EDF7;">=</td> <td style="border: 1px solid #D9EDF7; text-align: left; padding: 8px; background-color: #D9EDF7;">XVII</td> </tr> <tr style="background-color: #D9EDF7;"> <td style="border: 1px solid #D9EDF7; text-align: right; padding: 8px; background-color: #D9EDF7;">10</td> <td style="border: 1px solid #D9EDF7; text-align: center; padding: 8px; background-color: #D9EDF7;">+</td> <td style="border: 1px solid #D9EDF7; text-align: left; padding: 8px; background-color: #D9EDF7;">5</td> <td style="border: 1px solid #D9EDF7; text-align: center; padding: 8px; background-color: #D9EDF7;">+</td> <td style="border: 1px solid #D9EDF7; text-align: left; padding: 8px; background-color: #D9EDF7;">1</td> <td style="border: 1px solid #D9EDF7; text-align: center; padding: 8px; background-color: #D9EDF7;">+</td> <td style="border: 1px solid #D9EDF7; text-align: left; padding: 8px; background-color: #D9EDF7;">1</td> <td style="border: 1px solid #D9EDF7; text-align: center; padding: 8px; background-color: #D9EDF7;">=</td> <td style="border: 1px solid #D9EDF7; text-align: left; padding: 8px; background-color: #D9EDF7;">17</td> </tr> </table> 表示羅馬數字可以使用減法來取代加法的規則。例如四可以不用四個一相加來表示 IIII,而採用五減一來表示 IV。利用這類規則,羅馬人能夠減化許多數目的表示方式,例如 IX 取代 VIIII 表示 9,及 CD 取代 CCCC 表示 400。 今日我們並不確定羅馬符號的起源為何。例如符號 V 的起源主要有兩個理論。有些學者認為五最早是用握拳、拇指在外的手勢來表示。最後以象形文字書寫而簡化為 V。 另一個理論認為 X 源自在 10 條線加上交叉線。因此五可以表示為 X 的一半,或是 V。 羅馬數字可以很容易地用來相加或相減,但算起乘除法就相當不順手。這就是為什麼現在羅馬數字並不常用的原因了。 **問題** 然而,羅馬數字還是經常用於書本章節及頁碼的編號。在這一題工作是讀入兩個正整數,然後輸出兩個數字差的絕對值。所有的數字都必須以羅馬數字來表示。而連續四個相同符號出現時,必須用減法規則來化簡之。 >輸入說明 每個輸入檔中會有一個或以上的測試資料。每一行由兩個數字組成一筆測試資料,且所有數字將會小於4,000。檔案最後會以符號 # 表示結束。 >輸出說明 每筆測試資料的答案必須輸出到檔案中,並且換行。如果答案為零,則須輸出字串 ZERO > 標籤 進位制 ::: ### 範例輸入&輸出 :::info > 範例輸入 #1 I I MM II \# > 範例輸出 #1 ZERO MCMXCVIII ::: <a id=a013-1></a> ### 題解Part1 羅馬數字-->阿拉伯數字 :::warning **原理:** 這個部分比較好想,只要判斷每個羅馬數字符號是被加的還是被減的就好了。判斷方法也很簡單——**只要他後面的數字比它大,它就是被減的,不然就是被加的** (Eg: `IV`中的`I`, `XC`中的`X`, `CD`中的`C`分別是代表`-1`, `-10`, `-100`的意思; `III`中的`I`, `DCC`中的`D`和`C`則是代表`+1`, `+100`, `+500`。 **作法:** 在寫程式的時候我們可以建立一個`map(在python叫做dict)`裡面長這樣: <table style="width:100%; border-collapse: collapse;"> <tr style="background-color: #EFEBD7;"> <th style="width: 25%; border: 1px solid #ddd; padding: 8px;">Key</th> <th style="width: 25%; border: 1px solid #ddd; padding: 8px;">Value</th> <th style="width: 25%; border: 1px solid #ddd; padding: 8px;">Key</th> <th style="width: 25%; border: 1px solid #ddd; padding: 8px;">Value</th> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">I</td> <td style="border: 1px solid #ddd; padding: 8px;">1</td> <td style="border: 1px solid #ddd; padding: 8px;">V</td> <td style="border: 1px solid #ddd; padding: 8px;">5</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">X</td> <td style="border: 1px solid #ddd; padding: 8px;">10</td> <td style="border: 1px solid #ddd; padding: 8px;">L</td> <td style="border: 1px solid #ddd; padding: 8px;">50</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">C</td> <td style="border: 1px solid #ddd; padding: 8px;">100</td> <td style="border: 1px solid #ddd; padding: 8px;">D</td> <td style="border: 1px solid #ddd; padding: 8px;">500</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">M</td> <td style="border: 1px solid #ddd; padding: 8px;">1,000</td> <td style="border: 1px solid #ddd; padding: 8px;">~</td> <td style="border: 1px solid #ddd; padding: 8px;">~</td> </tr> </table> 就可以使用`for-loop`遍歷每一個羅馬數字符號,把它和它的下一個抓出來後比較,並用第一段說的規則判斷出該符號是被加的還是被減的。 **舉例:** 要轉換`CXLIX`成149時: 會長這樣 <table style="width:100%; border-collapse: collapse;"> <tr style="background-color: #EFEBD7;"> <th style="border: 1px solid #ddd; padding: 8px;">抓出的符號</th> <th style="border: 1px solid #ddd; padding: 8px;">它的下一個</th> <th style="border: 1px solid #ddd; padding: 8px;">比較</th> <th style="border: 1px solid #ddd; padding: 8px;">結果</th> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">C</td> <td style="border: 1px solid #ddd; padding: 8px;">X</td> <td style="border: 1px solid #ddd; padding: 8px;">C>X</td> <td style="border: 1px solid #ddd; padding: 8px;">C是+100</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">X</td> <td style="border: 1px solid #ddd; padding: 8px;">L</td> <td style="border: 1px solid #ddd; padding: 8px;">X<L</td> <td style="border: 1px solid #ddd; padding: 8px;">X是-10</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">L</td> <td style="border: 1px solid #ddd; padding: 8px;">I</td> <td style="border: 1px solid #ddd; padding: 8px;">L>I</td> <td style="border: 1px solid #ddd; padding: 8px;">L是+50</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">I</td> <td style="border: 1px solid #ddd; padding: 8px;">X</td> <td style="border: 1px solid #ddd; padding: 8px;">I<X</td> <td style="border: 1px solid #ddd; padding: 8px;">I是-1</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">X</td> <td style="border: 1px solid #ddd; padding: 8px;">沒了</td> <td style="border: 1px solid #ddd; padding: 8px;">X>沒東西</td> <td style="border: 1px solid #ddd; padding: 8px;">X是+10</td> </tr> <tr style="background-color: #EFEBD7;"> <td style="border: 1px solid #ddd; padding: 8px;text-align: center;" colspan="4">~end~</td> </tr> </table> 最後的結果就是$100-10+50-1+10 = 149$ ::: <a id=a013-2></a> ### 題解Part2 阿拉伯數字-->羅馬數字 ::: warning 先講作法,再講原理。大概會好懂一點 **作法:** 在這裡我們會建立一個長這樣的map: <table style="width:100%; border-collapse: collapse;"> <tr style="background-color: #EFEBD7;"> <th style="width: 25%; border: 1px solid #ddd; padding: 8px;">Key</th> <th style="width: 25%; border: 1px solid #ddd; padding: 8px;">Value</th> <th style="width: 25%; border: 1px solid #ddd; padding: 8px;">Key</th> <th style="width: 25%; border: 1px solid #ddd; padding: 8px;">Value</th> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">1000</td> <td style="border: 1px solid #ddd; padding: 8px;">M</td> <td style="border: 1px solid #ddd; padding: 8px;">40</td> <td style="border: 1px solid #ddd; padding: 8px;">XL</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">900</td> <td style="border: 1px solid #ddd; padding: 8px;">CM</td> <td style="border: 1px solid #ddd; padding: 8px;">10</td> <td style="border: 1px solid #ddd; padding: 8px;">X</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">500</td> <td style="border: 1px solid #ddd; padding: 8px;">D</td> <td style="border: 1px solid #ddd; padding: 8px;">9</td> <td style="border: 1px solid #ddd; padding: 8px;">IX</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">400</td> <td style="border: 1px solid #ddd; padding: 8px;">CD</td> <td style="border: 1px solid #ddd; padding: 8px;">5</td> <td style="border: 1px solid #ddd; padding: 8px;">V</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">100</td> <td style="border: 1px solid #ddd; padding: 8px;">C</td> <td style="border: 1px solid #ddd; padding: 8px;">4</td> <td style="border: 1px solid #ddd; padding: 8px;">IV</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">90</td> <td style="border: 1px solid #ddd; padding: 8px;">XC</td> <td style="border: 1px solid #ddd; padding: 8px;">1</td> <td style="border: 1px solid #ddd; padding: 8px;">I</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">50</td> <td style="border: 1px solid #ddd; padding: 8px;">L</td> <td style="border: 1px solid #ddd; padding: 8px;">~</td> <td style="border: 1px solid #ddd; padding: 8px;">~</td> </tr> </table> 處理流程是 從map的第一個`key`開始,一直減那個數字減到減到不能減,再換下一個`key`。重複那個過程到`key=1`結束為止。每減一次就印出那個`key`所對應的`value`,程式跑完答案就出來了。 _(有點像是a010因數分解那一題)_ **舉例**: 把`2964`轉換成`MMCMLXIV` <table style="width:100%; border-collapse: collapse;"> <tr style="background-color: #EFEBD7;"> <th style="border: 1px solid #ddd; padding: 8px;">Step...</th> <th style="border: 1px solid #ddd; padding: 8px;">減掉的數字</th> <th style="border: 1px solid #ddd; padding: 8px;">輸出的符號</th> <th style="border: 1px solid #ddd; padding: 8px;">剩下~</th> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">1</td> <td style="border: 1px solid #ddd; padding: 8px;">1000</td> <td style="border: 1px solid #ddd; padding: 8px;">M</td> <td style="border: 1px solid #ddd; padding: 8px;">1964</td> </tr> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">2</td> <td style="border: 1px solid #ddd; padding: 8px;">1000</td> <td style="border: 1px solid #ddd; padding: 8px;">M</td> <td style="border: 1px solid #ddd; padding: 8px;">964</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">3</td> <td style="border: 1px solid #ddd; padding: 8px;">900</td> <td style="border: 1px solid #ddd; padding: 8px;">CM</td> <td style="border: 1px solid #ddd; padding: 8px;">64</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">4</td> <td style="border: 1px solid #ddd; padding: 8px;">50</td> <td style="border: 1px solid #ddd; padding: 8px;">L</td> <td style="border: 1px solid #ddd; padding: 8px;">14</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">5</td> <td style="border: 1px solid #ddd; padding: 8px;">10</td> <td style="border: 1px solid #ddd; padding: 8px;">X</td> <td style="border: 1px solid #ddd; padding: 8px;">4</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px;">6</td> <td style="border: 1px solid #ddd; padding: 8px;">4</td> <td style="border: 1px solid #ddd; padding: 8px;">IV</td> <td style="border: 1px solid #ddd; padding: 8px;">0</td> </tr> <tr style="background-color: #EFEBD7;"> <td style="border: 1px solid #ddd; padding: 8px;text-align: center;" colspan="4">~end~</td> </tr> </table> 最後把答案組合起來就會變成: $MMCMLXIV$ **原理:** 其實這個作法背後的原理要講...還需要一個表比較好理解 <table style="width:100%; border-collapse: collapse;"> <tr style="background-color: #EFEBD7;"> <th style="width: 25%; border: 1px solid #ddd; padding: 8px; text-align: center;">阿拉伯數字</th> <th style="width: 25%; border: 1px solid #ddd; padding: 8px; text-align: center;">羅馬數字</th> <th style="width: 25%; border: 1px solid #ddd; padding: 8px; text-align: center;">阿拉伯數字</th> <th style="width: 25%; border: 1px solid #ddd; padding: 8px; text-align: center;">羅馬數字</th> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">1</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">I</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">6</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">VI</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">2</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">II</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">7</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">VII</td> </tr> <!-- 其他數字的行 --> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">3</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">III</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">8</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">VIII</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">4</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">IV</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">9</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">IX</td> </tr> <tr style="background-color: #F4F0DC;"> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">5</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">V</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">10</td> <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">X</td> </tr> </table> 如果不考慮`4`, `9`這兩個數字,1~10的羅馬數字都是基於加法運算來表示的。這個時候要轉換就簡單啦\~開一個`{10, 5, 1}`的map 從大的開始一直減減減就結束了。 但是,加了`4`, `9`這兩個用減法表示的數字就不一樣了。持續使用上面的做法的話,`9`會變成`VIII`(X), 4會變成`IIII`(X)。除了特別使用`if-else`等等作法特別考慮這些情況之外,有一個更好的方法——就是把map改成`{10, 9, 5, 4, 1}`。因為上述方法是**從大的數字往小的數字減的**,所以在數字是9的時候,程式會直接把數字減9然後輸出`IX`,而不會發生減5減4輸出`VIV`等荒謬情況。map的設計是Part2解法的關鍵。 ::: ### Python > 程式碼 ```python= #題解Part1: def rom_to_ara(romnum): #建立dict translation = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500,'M': 1000} result = 0 #比較每一個字母和它的下一個,為了避免IndexError,迴圈只有算到倒數第二個 for idx in range(len(romnum)-1): now = translation[ romnum[idx] ] nex = translation[ romnum[idx+1] ] if (now>=nex): result+=now else: result-=now result += translation[ romnum[-1] ] #把最後一個字母補算完 return result #題解Part2: def ara_to_rom(aranum): if aranum==0: return "ZERO" #建立dict translation = {1000: "M", 900: "CM", 500: "D", 400: "CD", 100: "C", 90: "XC", 50: "L", 40: "XL", 10: "X", 9: "IX", 5: "V", 4: "IV", 1:"I" } result = "" #從第一個key開始,開始減減減減到最後一個 for key, value in translation.items(): while aranum >= key: aranum -= key result = result + value return result #主程式開始 inp = input() while inp != "#": roma, romb = inp.split() araa, arab = rom_to_ara(roma), rom_to_ara(romb) araans = abs(araa-arab) print(ara_to_rom(araans)) inp = input() ``` _註1: `mydict.items()`會把mydict中的keys和values抓出來,兩兩一組組成tuple,最後包成`dict_items`物件回傳。`dict_items`可以用迭代器(`iter() + next()`, `for-loop`...)遍歷,但是不能隨機訪問(`~~[idx]`),所以通常會轉成list來用_ ### C > 程式說明 :::success 因為C沒有map這種好東西,所以C的程式碼會和題解說的有點不一樣 #### Part1: 羅馬數字符號-->阿拉伯數字,可以另外寫一個函式來取代map的功能 (下面寫作romc_to_aran) #### Part2: 阿拉伯數字-->羅馬數字,可以用兩個陣列來取代map。還有,要把阿拉伯數字答案再存入字元陣列中太麻煩了,下面的程式採用一邊轉換一邊輸出的方法。 ::: > 程式碼 ```c== #include <stdio.h> #include <string.h> //strlen #include <stdlib.h> //abs int romc_to_aran(char romc){ switch(romc){ case 'I': return 1; case 'V': return 5; case 'X': return 10; case 'L': return 50; case 'C': return 100; case 'D': return 500; case 'M': return 1000; default: return -999; } } int rom_to_ara(char romnum[]){ int result = 0; int length = strlen(romnum); //比較每一個字母和它的下一個,為了避免超出string長度範圍,迴圈只有算到倒數第二個 for (int idx=0; idx<length-1; idx++){ int now = romc_to_aran( romnum[idx] ); int next = romc_to_aran( romnum[idx+1] ); if (now>=next) result+=now; else result-=now; } result += romc_to_aran( romnum[length-1] ); //把最後一個字母補算完 return result; } void ara_to_rom(int aranum){ if (aranum==0){ printf("ZERO\n"); return; //提前終止函式 } //建立兩個陣列,兩個長度都是13,下面會用到 int arakey[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}; char* romvalue[]={"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"}; for (int idx=0; idx<13; idx++){ int key = arakey[idx]; char* value = romvalue[idx]; while (aranum>=key){ aranum -= key; printf("%s", value); } } printf("\n"); //換行 } int main(){ char roma[16], romb[16]; while (scanf("%s %s", roma, romb)==2){ int arra = rom_to_ara(roma); int arrb = rom_to_ara(romb); int araans = abs(arra-arrb); ara_to_rom(araans); } return 0; } ``` _註1: 題目有說所有數字小於4000。羅馬數字最長也就3888(`MMMDCCCLXXXVIII`)一共15位。所以讀取輸入的字串(`char[]`)只要開15+1(`\0`)個就好_ _註2: 第55行的 `char* romvalue[]` 在這裡是宣告一個指向`char[]`的指針陣列_ ### C++ > 程式碼 ```cpp=1 #include <iostream> #include <map> #include <string> #include <functional> //greater using namespace std; //題解Part1: int rom_to_ara(string romnum){ //建立map map<char, int> translation = { {'I', 1}, {'V', 5}, {'X', 10}, {'L', 50}, {'C', 100}, {'D', 500}, {'M', 1000}}; int result = 0; //比較每一個字母和它的下一個,為了避免超出string長度範圍,迴圈只有算到倒數第二個 for (int idx=0; idx<romnum.size()-1; idx++){ int now = translation[ romnum[idx] ]; int next = translation[ romnum[idx+1] ]; if (now>=next) result+=now; else result-=now; } result += translation[ romnum.back() ]; //把最後一個字母補算完 return result; } string ara_to_rom(int aranum){ if (aranum==0) return "ZERO"; //建立map map<int, string, greater<int>> translation = { {1000, "M"}, {900, "CM"}, {500, "D"}, {400, "CD"}, {100, "C"}, {90, "XC"}, {50, "L"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"}}; string result = ""; //從第一個key開始,開始減減減減到最後一個 for (pair<const int, string> &data: translation){ int key = data.first; string value = data.second; while (aranum >= key){ aranum -= key; result = result+value; } } return result; } int main(){ string roma, romb; while(cin>>roma>>romb){ int arra = rom_to_ara(roma), arrb = rom_to_ara(romb); int araans = abs(arra-arrb); cout << ara_to_rom(araans) << endl; } } ``` _註1:functional\-std\:\:greater: 詳細的資訊大家可以去google, 這裡就講結論就好了。`greater`放在`priority_queue`, `sort`, `map`, `set`這種可以自訂排序規則的容器時,它會讓容器內的元素**由大到小排**。_ _在本程式中,為了讓translation的key值是遞減排序的,所以map在初始化時放了`greater<int>`來排序key值_ _註2: `while(cin>>roma>>romb)`寫的時候的想法: `cin`在`failbit`(錯誤讀取)或是`eofbit`(輸入檔到底了)被設置時,cin會回傳false。_ _因此可以利用這個特性。當程式讀取到會後一行`#`時, `#`會存入roma,然後就沒有東西可以讀入romb了。因此`cin`會回傳false,進而跳脫`while-loop`_ ### Java > 程式碼 ```java=1 import java.util.Scanner; import java.util.TreeMap; import java.util.Map; //for Map.Entry import java.util.Comparator; //for Comparator.reverseOrder public class Main{ //題解Part1 private static int rom_to_ara(String romnum){ //建立map TreeMap<Character, Integer> translation = new TreeMap<>(); translation.put('I', 1); translation.put('V', 5); translation.put('X', 10); translation.put('L', 50); translation.put('C', 100); translation.put('D', 500); translation.put('M', 1000); int result=0; //比較每一個字母和它的下一個,為了避免超出string長度範圍,迴圈只有算到倒數第二個 for (int idx = 0; idx < romnum.length() - 1; idx++) { int now = translation.get( romnum.charAt(idx) ); int next = translation.get( romnum.charAt(idx + 1) ); if (now >= next) result += now; else result -= now; } //把最後一個字母補算完 result += translation.get( romnum.charAt(romnum.length() - 1) ); return result; } //題解Part2 private static String ara_to_rom(int aranum){ if (aranum == 0) return "ZERO"; //建立map TreeMap<Integer, String> translation = new TreeMap<>(Comparator.reverseOrder()); translation.put(1000, "M"); translation.put(900, "CM"); translation.put(500, "D"); translation.put(400, "CD"); translation.put(100, "C"); translation.put(90, "XC"); translation.put(50, "L"); translation.put(40, "XL"); translation.put(10, "X"); translation.put(9, "IX"); translation.put(5, "V"); translation.put(4, "IV"); translation.put(1, "I"); StringBuilder result = new StringBuilder(); //從第一個key開始,開始減減減減到最後一個 for (Map.Entry<Integer, String> data : translation.entrySet()) { int key = data.getKey(); String value = data.getValue(); while (aranum >= key) { aranum -= key; result.append(value); } } return result.toString(); } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); while (true) { String roma = scanner.next(); if ("#".equals(roma)) { break; //碰到#就結束了 } String romb = scanner.next(); int araa = rom_to_ara(roma); int arab = rom_to_ara(romb); int araans = Math.abs(araa-arab); System.out.println(ara_to_rom(araans)); } scanner.close(); } } ``` _註1: `Comparator.reverseOrder()`: 會回傳一個比較器(comparator),放在`TreeMap`, `TreeSet`, `sort`...時,可以讓元素**反自然排序**。如果是數字就是由大到小排 (Eg: `2 3 1` --> `3 2 1`),字串就是...字典排序後反過來(Eg: `banana cherry apple` --> `cherry banana apple`)。_ _在本程式中,為了讓translation的key值是遞減排序的,所以map在初始化時放了`Comparator.reverseOrder()`來排序key值_ _註2: `Map.Entry`: 用`Map.Entry`建立的物件可以存某個map的key&value,用`entry.getKey()` `entry.getValue()`可以把值給提出來。_ _比起把一個map的keys(`.keySet()`)和values(`.values()`)抓出來再遍歷,使用Map.Entry會直觀一點。 (只是要`import java.util.Map`就是了030)_ <a id=a015></a> ## zerojudge-a015 矩陣的翻轉 ### [題目](https://zerojudge.tw/ShowProblem?problemid=a015) :::info > 內容 已知一(m x n)矩陣**A**,我們常常需要用到另一個將**A**中之行與列調換的矩陣。這個動作叫做矩陣的翻轉。舉例來說, 若**A=** <table style="width:100%; border-collapse: collapse;"> <tr style="background-color: #D6EAF4;"> <td style="width: 20%; border: 1px solid #ddd; padding: 8px;">3</td> <td style="width: 20%; border: 1px solid #ddd; padding: 8px;">1</td> <td style="width: 20%; border: 1px solid #ddd; padding: 8px;">2</td> </tr> <tr style="background-color: #D6EAF4;"> <td style="width: 20%; border: 1px solid #ddd; padding: 8px;">8</td> <td style="width: 20%; border: 1px solid #ddd; padding: 8px;">5</td> <td style="width: 20%; border: 1px solid #ddd; padding: 8px;">4</td> </tr> </table> 則**AT=** <table style="width:100%; border-collapse: collapse;"> <tr style="background-color: #D6EAF4;"> <td style="width: 33%; border: 1px solid #ddd; padding: 8px;">3</td> <td style="width: 33%; border: 1px solid #ddd; padding: 8px;">8</td> </tr> <tr style="background-color: #D6EAF4;"> <td style="width: 33%; border: 1px solid #ddd; padding: 8px;">1</td> <td style="width: 33%; border: 1px solid #ddd; padding: 8px;">5</td> </tr> <tr style="background-color: #D6EAF4;"> <td style="width: 33%; border: 1px solid #ddd; padding: 8px;">2</td> <td style="width: 33%; border: 1px solid #ddd; padding: 8px;">4</td> </tr> </table> 現在 請您針對所讀取到的矩陣進行翻轉。 >輸入說明 第一行會有兩個數字,分別為 列(row)<100 和 行(column)<100,緊接著就是這個矩陣的內容 ** 測資檔會包含多組矩陣資料 >輸出說明 直接輸出翻轉後的矩陣 > 標籤 二維陣列 ::: ### 範例輸入&輸出 :::info >範例輸入 #1 2 3 3 1 2 8 5 4 >範例輸出 #1 3 8 1 5 2 4 ::: ### 題解 :::warning 這個所謂的"矩陣的翻轉"其實有一個專有名詞,叫做**轉置(transpose)**,也就是把陣列的行和列互換。 舉例來說,有個$2\times3$的矩陣A長這樣: $$ A = \begin{pmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{pmatrix} $$ 那他的轉置矩陣AT就會是一個$3\times2$的矩陣(因為A的行換到AT的列了),長這樣: $$ A^T = \begin{pmatrix} 1 & 4 \\ 2 & 5 \\ 3 & 6 \end{pmatrix} $$ 換到矩陣的一般形式來看\~ 大小為$m\times n$原矩陣A和轉置矩陣AT: $$ A = \begin{pmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & \cdots & a_{mn} \end{pmatrix} ~~~~~~~~~~~~~~~ A^T = \begin{pmatrix} a_{11} & a_{21} & \cdots & a_{m1} \\ a_{12} & a_{22} & \cdots & a_{m2} \\ \vdots & \vdots & \ddots & \vdots \\ a_{1n} & a_{2n} & \cdots & a_{mn} \end{pmatrix} $$ 眼尖的各位應該發現了,轉置矩陣有幾個特性: * 原本矩陣是$m\times n$,新矩陣就是$n\times m$ * $AT_[c][r]~~=~~A[r][c]$ _(對沒錯前面鋪那個多梗只是為了講這個)_ ::: ### Python > 程式說明 ::: success 為了讓程式更清楚一點,寫了transpose函式,拿來讀取並轉置矩陣用 ::: > 程式碼 original ```python= import sys #該死的多筆輸入... def transpose(A, row, column): #先建立大小為column * row的空陣列 AT = [] for _ in range(column): AT.append( [0]*row ) #轉置 for c in range(column): for r in range(row): AT[c][r] = A[r][c] return AT for inp in sys.stdin: row, column = map(int, inp.split()) matrix = [] for _ in range(row): matrix.append( list(map(int, sys.stdin.readline() .split())) ) #轉置&輸出 ans = transpose(matrix, row, column) for ele in ans: print(*ele) ``` <a id=a015-1></a> _註1: 第27行的`*ele`: `*`在這裡的作用為**解包(unpacking)**,也就是把一個陣列(或是其他可以迭代的東西)拆解成很多個元素。運用場景蠻多的,舉例如下:_ ::: spoiler _有點長 有興趣的請自行點開來看_ 1. 假設你有一個函式`func(a, b, c)` 還有一個陣列`args = [1, 2, 3]` 你想要把arg的元素丟到func裡面去,可以這樣寫 ```python def func(a, b, c): #... pass args = [1, 2, 3] func(*args) #等同於func(1, 2, 3) ``` 2. 假設你有一個陣列`args = [1, 2, 3, 4, 5]` 你想要把他print出來。除了用`for-loop`或是`map(str, ...) + ''.join()`,還可以用`*` ```python args = [1, 2, 3, 4, 5] print(*args) #等同於print(1, 2, 3, 4, 5) #output: 1 2 3 4 5 #也可以改變中間要間隔甚麼喔 print(*args, sep="*") #output: 1*2*3*4*5 print(*args, sep="") #output: 12345 ``` 3. 假設你有一個陣列`args = [1, 2, 3, 4, 5]` 你想把第一個數字賦予變數a,其他全部丟到陣列b。除了用slice (`[start:end:step]` / `slice(start, end, step)`) 之外,你也可以這樣寫 ```python args = [1, 2, 3, 4, 5] a, *b = args print(a, b) #output 1 [2, 3, 4, 5] a, b, c, *d = args print(a, b, c, d) #output: 1 2 3 [4, 5] a, *b, c = args print(a, b, c) #ourput: 1 [2, 3, 4] 5 *a, *b = args #SyntaxError:不能在賦值的時候解包兩個東西(畢竟直譯器也不知道要怎麼分配嘛) #這個時候就乖乖用slice吧 a, b = args[:2], args[2:] print(a, b) #output: [1, 2] [3, 4, 5] ``` ::: <br> > 程式碼 list comprehension ```python= import sys def transpose(A, row, column): AT = [[A[r][c] for r in range(row)] for c in range(column)] return AT for inp in sys.stdin: row, column = map(int, inp.split()) matrix = [list(map(int, sys.stdin.readline().split())) for _ in range(row)] ans = transpose(matrix, row, column) for ele in ans: print(*ele) ``` _註1: 第4, 11行的list comprehension(中文翻譯蠻多的: 列表理解,列表推導式...)簡介_ ::: spoiler _有點長 有興趣的請自行點開_ * list comprehension的結構 (乾好像在學英文文法) * 一般結構: `[表達式 for 元素 in 可迭代物件]` * 在for後面加上if,只有條件成立時元素才會加入list,達成過濾的效果。 長這樣: `[表達式 for 元素 in 可迭代物件 if 條件]` 好啦我知道那個很難看懂。來個舉例 1. 假設你想要建立一個陣列 裡面有1~5的平方數。除了用`for-loop`,你還可以這樣寫 ```python square = [num**2 for num in range(1, 6)] print(square) #output: [1, 4, 9, 16, 25] ``` 2. 假設你想要建立一個陣列 裡面有1~10中的偶數的平方數。除了用`for-loop`,你還可以這樣寫 ```python even_square = [num**2 for num in range(1, 11) if num%2==0] #想這樣寫也...可以啦 even_square = [num**2 for num in range(2, 11, 2)] print(even_square) #output: [4, 16, 36, 64, 100] ``` 3. **表達式裡面還可以再塞一個if-else** 假設你有一個陣列`args = [1, 4, 3]` 你想要建立一個陣列,裡面儲存args的每一個元素是奇數還是偶數。除了用`for-loop`或是`map()`,你還可以這樣寫 ```python args = [1, 4, 3] judge = ["even" if ele%2==0 else "odd" for ele in args] print(judge) #output: ['odd', 'even', 'odd'] ``` _註1-1: `"even" if ele%2==0 else "odd"` 這個東西的意思是: **如果`ele%2==0`(條件)成立就回傳`even`, 不成立就回傳`odd`**。 用法有點類似C\+\+ Java的`條件? 表達式1:表達式2`_ _註1-2: 註1-1的東西其實有個專有名詞,叫做三元運算子(Ternary Operator)。他的名字沒有很重要就是了,會用就好_ 4. **一個方括號內是可以放兩個for的。** 假設你有兩個陣列`arga=[1, 2, 3]`, `argb=[4, 5, 6]`,你想要把arga, argb的所有組合方式列出來。除了用`for-loop`,你還可以這樣 ```python arga = [1, 2, 3] argb = [4, 5, 6] pairs = [(elea, eleb) for eleb in argb for elea in arga] print(pairs) #output: [(1, 4), (2, 4), (3, 4), (1, 5), (2, 5), (3, 5), (1, 6), (2, 6), (3, 6)] ``` 5. **表達式的if else(三元運算子) 和 list comprehesion的if(拿來篩選) 是分開的,也就是說可以同時出現** 假設你有一個陣列`args = [1, 3, 5, 7, 8, 11]` 你想要把介於3~10的數字依照以下規則轉換,並塞入一個list中。 * 如果數字是奇數,把它減1 * 如果數字是偶數,把它加1 除了用`for-loop`+`if-else`,你還可以這樣寫 ```python args = [1, 3, 5, 7, 8, 11] #小括號只是為了方便理解,沒一定要加 ans = [(ele+1 if ele%2==0 else ele-1) for ele in args if 3<= ele <=10] ans = [ele^1 for ele in args if 3<= ele <=10] #其實也可以用位元運算 print(ans) #output: [2, 4, 6, 9] ``` 6. **list comprehension裡面可以再放一個list comprehension** 假設你有一個二維陣列`A = [[1, 2, 3], [4, 5, 6]]`,你想要建立一個陣列B,內容和A一模一樣。你很任性不想用`.copy()`或是`for-loop`,就是想用list comprehension,你可以這樣寫: _(對不起我知道這個例子超爛)_ ```python A = [[1, 2, 3], [4, 5, 6]] row = 2 col = 3 B = [[A[r][c] for c in range(col)] for r in range(row)] print(B) #output: [[1, 2, 3], [4, 5, 6]] ``` ::: <br> > 程式碼 zip ```python= import sys def transpose(A): AT = list(zip(*A)) return AT for inp in sys.stdin: row, column = map(int, inp.split()) matrix = [list(map(int, sys.stdin.readline().split())) for _ in range(row)] ans = transpose(matrix) for ele in ans: print(*ele) ``` _註1: `zip(*A)`意思: `*`拿來解包的用法請見[第一個程式碼的註解](#a015-1)。在這裡,`*`會先把二維陣列解包成多個一維陣列,再用`zip()`包起來。以達到轉置矩陣的目的。_ ::: spoiler 例如: _(嘿舉例還是有點長 ~~今天幹話很多~~ ,有興趣的請自行點開來看)_ 原本矩陣A長這樣 ```python A = [ [1, 2, 3], [4, 5, 6] ] ``` 把A解包後會變成兩個陣列 ```python [1, 2, 3] [4, 5, 6] ``` 這個時候zip起來會變成一個zip物件,裡面長這樣 (zip物件裡裝的是tuple喔) ```python (1, 4), (2, 5), (3, 6) ``` 最後再用`list()`包起來就可以使用了。轉置矩陣AT: ```python [(1, 4), (2, 5), (3, 6)] ``` ::: ### C > 程式說明 ::: success 為了讓程式更清楚一點,寫了transpose函式,拿來讀取並轉置矩陣用 ::: > 程式碼 ```c=1 #include <stdio.h> void transpose(int A[100][100], int AT[100][100], int row, int column) { for (int c = 0; c < column; c++) { for (int r = 0; r < row; r++) { AT[c][r] = A[r][c]; } } } int main() { int row, column; int matrix[100][100], ans[100][100]; while(scanf("%d %d", &row, &column) != EOF){ //見註1 // 讀取輸入 for (int r = 0; r < row; r++) { for (int c = 0; c < column; c++) { scanf("%d", &matrix[r][c]); } } // 轉置 transpose(matrix, ans, row, column); //見註2 // 輸出 for (int r = 0; r < column; r++) { for (int c = 0; c < row; c++) { printf("%d ", ans[r][c]); } printf("\n"); } } return 0; } ``` _註1: C的`scanf()`會回傳它讀到了幾個東西,如果讀到檔案尾部(End-Of-File),就會回傳`EOF`。這裡也可以寫成` while (scanf("%d %d", &row, &column) == 2) {`_ _註2: `transpose(matrix, ans, row, column);`: `matrix`, `ans`兩個原生陣列被當作參數傳入函式時,其實是傳入那兩個陣列的記憶體位址。所以transpose在修改`AT`的內容時,其實就是在修改main中`ans`的內容。 (C++也是喔)_ ### C++ > 程式說明 ::: success 為了讓程式更清楚一點,寫了transpose函式,拿來讀取並轉置矩陣用 ::: > 程式碼 ```cpp=1 #include <iostream> #include <vector> //想要用函式處理轉置+int**表示矩陣太麻煩=>用vector using namespace std; vector<vector<int>> transpose(vector<vector<int>> &A, int row, int column){ //先建立大小為column, row的陣列 vector<vector<int>> AT = vector<vector<int>>(column, vector<int>(row)); //轉置 for (int c=0; c<column; c++){ for (int r=0; r<row; r++){ AT[c][r] = A[r][c]; } } return AT; } int main(){ int row, column; while (cin >> row >> column){ //讀取輸入 vector<vector<int>> matrix=vector<vector<int>>(row, vector<int>(column)); for (int r=0; r<row; r++) for (int c=0; c<column; c++) cin>>matrix[r][c]; //轉置 vector<vector<int>> ans = transpose(matrix, row, column); //輸出 for (vector<int> &rows: ans){ for (int ele: rows){ cout << ele << ' '; } cout << endl; } } return 0; } ``` ### Java > 程式說明 ::: success 為了讓程式更清楚一點,寫了transpose函式,拿來讀取並轉置矩陣用 ::: > 程式碼 ```java=1 import java.util.Scanner; public class Main{ private static int[][] transpose(int[][] A, int row, int column){ //先建立大小為column, row的陣列 int [][] AT = new int[column][row]; //轉置 for (int c=0; c<column; c++){ for (int r=0; r<row; r++){ AT[c][r] = A[r][c]; } } return AT; } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); while (scanner.hasNext()){ //EOF處理 //讀取輸入 int row = scanner.nextInt(); int column = scanner.nextInt(); int[][] matrix = new int[row][column]; for (int r = 0; r < row; r++) { for (int c = 0; c < column; c++) { matrix[r][c] = scanner.nextInt(); } } //轉置 int[][] ans = transpose(matrix, row, column); //輸出 for (int[] rows: ans){ for (int ele: rows){ System.out.print(ele+" "); } System.out.println(); } } scanner.close(); } } ```