---
tags : 初階班
---
# 10th 初階班上學期期末考 題解
## 競賽用hello world
老樣子,送分題
:::spoiler `c`
```c=
#include<stdio.h>
int main() {
printf("hello, world\n");
return 0;
}
```
:::
:::spoiler `c++`
```cpp=
#include <iostream>
using namespace std;
int main() {
cout << "hello, world\n";
return 0;
}
```
:::
:::spoiler `python`
```python=
print("hello, world")
```
:::
## A. 為各位的期中考獻上祝福
這題直接用 `long long` 或 `unsigned long long` 都是 `NA 59%`
正解:
1. 兩數同號,用 `unsigned long long`
2. 兩數異號,直接 `a + b`
3. 若 $a = b = -2^{63}$,需要特判,因為 `unsigned long long` 裝不下。
:::spoiler `code`
```cpp=
#include<bits/stdc++.h>
#define LL long long
using namespace std;
int main() {
LL a, b;
cin >> a >> b;
if (a < 0 ^ b < 0) cout << a + b;
else {
if (a == b && a == LLONG_MIN) cout << "-18446744073709551616";
else {
unsigned LL pa = abs(a), pb = abs(b);
if (a < 0) cout << '-';
cout << pa + pb;
}
}
return 0;
}
```
:::
另解:
使用毒瘤資料型態 `__int128` :poop:
為了讓各位覺得它有使用的價值,
先給各位看一下 `main()` 函數裡面的程式碼。
```cpp=
int main() {
__int128 a, b;
cin >> a >> b;
cout << a+b << '\n';
}
```
是不是很簡短!!!
但是它全部的程式碼卻有點複雜,
有興趣的可以參考看看。
:::spoiler `code`
```cpp=
#include <bits/stdc++.h>
using namespace std;
namespace int128IO {
istream& operator>>(istream& is, __int128& i) {
string s; is>>s; i = 0;
auto c=s.begin(); c+=(s[0]=='-');
for(; c!=s.end(); ++c) i=i*10+(*c-'0');
if(s[0]=='-') i=-i;
return is;
}
ostream& operator<<(ostream& os, __int128 i) {
string s; bool neg=false; if(i<0) neg=true, i=-i;
while(i) s+=('0'+i%10), i/=10;
if(neg) os<<'-';
for(auto c=s.rbegin();c!=s.rend();++c) os<<*c;
return os;
}
}
using namespace int128IO;
int main() {
__int128 a, b;
cin >> a >> b;
cout << a+b << '\n';
}
```
:::
## B.小青蛙吃蒼蠅
這題應該非常簡單
只是有個小陷阱
那就是每隻青蛙每分鐘吃的蒼蠅數不一定是整數
所以可以先把所有數乘起來
最後再除
:::spoiler code
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main () {
int a1, a2, a3, b1, b2;
cin >> a1 >> a2 >> a3 >> b1 >> b2;
cout << a3 * b1 * b2 / a1 / a2;
}
```
:::
## C.《關於台民想建一棟叫台民館的台民館這件事》
這是一提水題
不過有個小陷阱
那就是要判斷**台民館是否能被順利建造**
如果面積是0
那就代表不會被建造 :poop:
:::spoiler code
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main () {
long long a, b, c;
while (cin >> a >> b >> c) {
long long maxn = a * b / 100;
cout << maxn << ' ';
if (max >= c && c != 0) cout << "yes\n";
else cout << "no\n";
}
}
```
:::
## D. 金斧頭一砍八
#### NA 60%
照著題敘一個一個把人砍掉即可
:::spoiler `code`
```cpp=
#include<bits/stdc++.h>
using namespace std;
constexpr int mod = 1e9 + 7;
int main() {
int T, n;
cin >> T;
while (T--) {
cin >> n;
bool alive[n + 1];
memset(alive, 1, sizeof(alive));
bool flag = true;
for (int i = 1, tmp = 0; tmp < n - 1; i == n ? i = 1 : i++) {
if (alive[i]) {
flag = !flag;
if (flag) alive[i] = 0, tmp++;
}
}
for (int i = 1; i <= n; i++) {
if (alive[i]) {cout << i << '\n'; break;}
}
}
return 0;
}
```
:::
#### AC
##### solve 1
其實砍人有其規律:
如果把編號 $1\sim n$ 砍完一圈算一輪,
假設現在是第 $r$ 輪,還有 $p$ 人未蹲下,且未蹲下的人中,編號最小的是 $k$,則:
1. 未蹲下的人中,相鄰編號差距為 $2^{r - 1}$
2. 從編號 $1\sim n$ 砍完,人會剩 $[\cfrac p2]$ 人
3. 若 $p$ 為奇數,則砍完人後,最小編號會變成 $k + 2^r$
如此就可以省去 `for` 迴圈從 $1$ 跑到 $n$ 的過程。
有趣的是,如果 $2^r$ 用 `pow` 的話,不僅慢,還可能有精度問題 (他是浮點數)
因此我們可以把 $2^0\sim 2^{30}$ 次方建出來,加快速度。
(由於 $n\in int$ 及第 3. 點證明,因此可以得知 $r\leq 30$)
:::spoiler `code`
```cpp=
#include<bits/stdc++.h>
#define LL long long
#define mem(x) memset(x, 0, sizeof(x))
#define all(x) x.begin(), x.end()
#define f first
#define s second
using namespace std;
constexpr int mod = 1e9 + 7;
int main() {
cin.tie(0), ios_base::sync_with_stdio(false);
int pow[31] = {1};
for(int i = 1; i <= 30; i++) pow[i] = pow[i - 1] * 2;
int T, n;
cin >> T;
while (T--) {
cin >> n;
int k = 1, r = 1;
while (n > 1) {
if (n % 2 == 1) { // if(n & 1)
k += pow[r]; // k += (1 << r)
}
n /= 2, r++; // n >>= 1, r++
}
cout << k << '\n';
}
return 0;
//上方程式碼註解是給進階班的 code,初階班可以不用建表就 AC
}
```
:::
##### solve 2
如果你花了很久的時間找數列的規律,
可以發現答案是:${1}, \{1, 3\}, \{1, 3, 5, 7\}, \{1, 3, 5, 7, 9, 11, 13, 15\}...$
可以轉換成數學式:$(n - 2^{[{\log_2}^n]})\times 2 + 1$
:::spoiler `code`
```cpp=
#include<bits/stdc++.h>
#define LL long long
using namespace std;
constexpr int mod = 1e9 + 7;
int main() {
cin.tie(0), ios_base::sync_with_stdio(false);
int pow[31] = {1};
for (int i = 1; i <= 30; i++) pow[i] = pow[i - 1] * 2;
int T, n;
cin >> T;
while (T--) {
cin >> n;
cout << (n - pow[__lg(n)]) * 2 + 1 << '\n';
//cout << (n - (1 << __lg(n)) << 1 | 1) << '\n';
}
return 0;
//上方程式碼註解是給進階班的 code,進階班可以不用建表就 AC
}
```
:::
## E.夏天
沒錯
又是暈船題
就照著題目所說的打就會過
:::spoiler code
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main () {
int q;
cin >> q;
while (q--) {
int n, m, k, x, y;
string str;
cin >> n >> m >> k >> x >> y >> str;
n %= 360;
if (n == 0 || n >= 270) {
int nowx = k / 2 + 1, nowy = k / 2 + 1;
x = k / 2 + 1 - x, y = k / 2 + 1 - y;
for (int i = 0; i < str.length(); i++) {
if (str[i] == 'U') nowx--;
else if (str[i] == 'D') nowx++;
else if (str[i] == 'L') nowy++;
else nowy--;
if (nowx == 0) nowx = 1;
else if (nowx == k + 1) nowx = k;
if (nowy == 0) nowy = 1;
else if (nowy == k + 1) nowy = k;
}
if (abs(nowx - x) <= 1 && abs(nowy - y) <= 1) cout << "You will find your true love!\n";
else cout << "Fxxk, I am lost\n";
}
else cout << "You should go home\n";
}
}
```
:::
## F.Q怎麼還在暈
這題是難得幾乎只能用遞迴的題目
如果$p>q$,那$n$必為偶數
如果$p<q$,那$n$必為奇數
所以只要這此規則遞迴到 $p=q$ 即可
:::spoiler code
```cpp=
#include <bits/stdc++.h>
using namespace std;
int trans(int p, int q) {
if (p == q) return 1;
else if (p < q) return 1 + trans(q, p);
else if (p > q) return trans(p - q, q) * 2;
}
int main() {
int p, q;
while (cin >> p >> q)
cout << trans(p, q) << '\n';
}
```
:::
## G.陣列刪除
這題應該需要小小的動腦
花費最少的情況就是優先刪最小的數
所以先把陣列sort一遍
找出最少的那些數即可
:::spoiler code
```cpp=
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
cin >> n;
int arr[n];
for(int i = 0 ; i < n; i++) cin >> arr[i];
sort (arr, arr + n);
int sum = 0 ;
for(int i = 0 ; i < n/2 ; i++) sum += arr[i];
cout << sum << '\n';
}
```
:::
## H.矩陣加密
這題稍微難上不少
首先你要知道矩陣乘法的規則
但這個從題目應該就可以觀察出規律
但要實踐它又有些難度
過了這兩關
應該就沒問題了
:::spoiler code
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main () {
string str;
int a, b, c, d;
cin >> str >> a >> b >> c >> d;
int l = str.length();
int arr[2][l / 2];
int key[2][2];
int ans[2][l / 2];
key[0][0] = a, key[0][1] = b, key[1][0] = c, key[1][1] = d;
for (int i = 0; i < l; i++) {
if (i < l / 2) arr[0][i] = (int)(str[i] - 65);
else arr[1][i - l / 2] = (int)(str[i] - 65);
}
for (int i = 0; i < 2; i++) for (int j = 0; j < l / 2; j++) ans[i][j] = 0;
for (int i = 0; i < 2; i++) for (int j = 0; j < l / 2; j++) for (int k = 0; k < 2; k++)
ans[i][j] += (key[i][k] * arr[k][j]);
for (int i = 0; i < 2; i++) for (int j = 0; j < l / 2; j++) cout << (char)(ans[i][j] % 26 + 65);
}
```
:::
## I. 持續演奏,直至心願實現
#### NA 10%
很簡單,把整個音譜跑過去,符合規則就是 $1$,不符合就是 $0$
#### NA 20%
暴搜 $w[i] = ?$ 的所有可能性
但是暴搜時,要記得:
1. 一個音符不能跨越兩個小節 $\implies$ `DFS` 時要有一個變數 `cur` 記錄當前拍數
2. 避免有「小數」拍子,可以將每個音節設定為 $\cfrac {16n}{m}$ 個十六分音符拍
3. 相對地,$k$ 分音符變 $\cfrac {16} k$ 拍
綜合以上 $3$ 點,我們可以知道 `check` 機制是:$cur \leq \cfrac{16n}{m}$
而這個 `DFS` 可以剪枝,如果當前節拍已經不符音譜規定,
那也不需要繼續遞迴後面的節拍了。
:::spoiler `code`
```cpp=
#include<bits/stdc++.h>
using namespace std;
constexpr int mod = 1e9 + 7;
int sum = 0, n, m, len, check;
string str;
void dfs(int i, int cur) {
if (i == len && cur == check) {sum++; return;}
cur %= check;
if (str[i] != '?') {
if (str[i] == 'M' && cur + 8 <= check) dfs(i + 1, cur + 8);
else if (str[i] == 'C' && cur + 4 <= check) dfs(i + 1, cur + 4);
else if (str[i] == 'Q' && cur + 2 <= check) dfs(i + 1, cur + 2);
else if (str[i] == 'S' && cur + 1 <= check) dfs(i + 1, cur + 1);
else return;
}
else {
if (cur + 8 <= check) dfs(i + 1, cur + 8);
if (cur + 4 <= check) dfs(i + 1, cur + 4);
if (cur + 2 <= check) dfs(i + 1, cur + 2);
if (cur + 1 <= check) dfs(i + 1, cur + 1);
}
}
int main() {
cin >> n >> m;
cin.get(), cin >> str;
len = str.size(), check = 16 / m * n;
dfs(0, 0), cout << sum;
return 0;
}
```
:::
#### NA 40%
從這裡開始就是使用 `DP` 了
在剛剛的 NA 20% 解中,我們可以發現:當前節拍數 `cur` 是很重要的,
所以我們可以建一個陣列,存下當前音符之下,各種拍數的可能數各為多少。
前 $20\%$ 的 `DP` 測資只有 $?$,所以只要知道 $?$ 的轉移式即可:
$dp[i][j] = dp[i - 1][j - 8] + dp[i - 1][j - 4] + dp[i - 1][j - 2] + dp[i - 1][j - 1]$
$i$ 是指當前第幾音符,$j$ 是指第幾節拍,
但是要注意溢位問題喔!
需判斷 $j - 8,\;j - 4,\;j - 2,\;j - 1\geq 0$
#### AC
先說一下,NA 60% 是給複雜度歪掉的人用的
直接來講如何 AC。
從 NA 40% 的解法可以知道:只差 $w[i]\neq \;?$ 的轉移式就 AC 了。
不難理解,其實 $?$ 的轉移式就是把 $\{M,\;C,\;Q,\;S\}$ 的轉移式相加
把他們分開就好。
因此,所有 $DP$ 轉移式如下:
$\displaystyle \begin{cases} \displaystyle \begin{cases} dp[i][j] = 0\\
dp[i][j] = dp[i][j] + dp[i - 1][j - 8]
\quad\quad if\quad j \geq 8\\
dp[i][j] = dp[i][j] + dp[i - 1][j - 4]
\quad\quad if\quad j \geq 4\\
dp[i][j] = dp[i][j] + dp[i - 1][j - 2]
\quad\quad if\quad j \geq 2\\
dp[i][j] = dp[i][j] + dp[i - 1][j - 1]
\quad\quad if\quad j \geq 1\\
\end{cases}
\quad\quad if\quad w[i] =\;?\\\\
dp[i][j] = dp[i - 1][j - 8]
\quad\quad if\quad (j \geq 8\quad and\quad w[i]=\;M)\\
dp[i][j] = dp[i - 1][j - 4]\quad\quad if\quad (j \geq 4\quad and\quad w[i]=\;C)\\
dp[i][j] = dp[i - 1][j - 2]\quad\quad if\quad (j \geq 2\quad and\quad w[i]=\;Q)\\dp[i][j] = dp[i - 1][j - 1]\quad\quad if\quad (j \geq 1\quad and\quad w[i]=\;S)\\dp[i][j] = 0\quad\quad\quad\quad\quad\;\;\;\quad\quad else \end{cases}$
:::spoiler `code_直觀`
```cpp=
#include<bits/stdc++.h>
#define LL long long
using namespace std;
constexpr int mod = 1e9 + 7;
LL dp[2][145] = {1}; //dp[0][0] = 1, 16 * n / m <= 144
int main() {
int n, m;
cin >> n >> m;
int p = 16 * n / m;
string w; cin >> w;
const int len = w.size();
for (size_t i = 0; i < w.size(); i++) {
for (int j = 1; j <= p; j++) {
if(w[i] == '?'){
dp[~i & 1][j] = 0;
if(j >= 8) dp[~i & 1][j] += dp[i & 1][j - 8];
if(j >= 4) dp[~i & 1][j] += dp[i & 1][j - 4];
if(j >= 2) dp[~i & 1][j] += dp[i & 1][j - 2];
if(j >= 1) dp[~i & 1][j] += dp[i & 1][j - 1];
dp[~i & 1][j] %= mod;
}
else if(w[i] == 'M' && j >= 8)
dp[~i & 1][j] = dp[i & 1][j - 8];
else if(w[i] == 'C' && j >= 4)
dp[~i & 1][j] = dp[i & 1][j - 4];
else if(w[i] == 'Q' && j >= 2)
dp[~i & 1][j] = dp[i & 1][j - 2];
else if(w[i] == 'S' && j >= 1)
dp[~i & 1][j] = dp[i & 1][j - 1];
else dp[~i & 1][j] = 0;
}
dp[~i & 1][0] = dp[~i & 1][p];
}
cout << dp[len & 1][0];
return 0;
}
```
:::
:::spoiler `code_稍微包裝`
```cpp=
#include<bits/stdc++.h>
#define LL long long
using namespace std;
constexpr int mod = 1e9 + 7;
int dp[2][145] = {1}; //dp[0][0] = 1, 16 * n / m <= 144
int main() {
int n, m; const int num[4] = {1, 2, 4, 8};
const string tile = "SQCM";
cin >> n >> m;
int p = 16 * n / m;
string w; cin >> w;
const int len = w.size();
for (size_t i = 0; i < w.size(); i++) {
for (int j = 1; j <= p; j++) {
bool flag = false;
if (w[i] == '?') dp[~i & 1][j] = 0, flag = true;
for (int k = 0; k < 4; k++) {
if (w[i] == tile[k] && j >= num[k]) {
dp[~i & 1][j] = dp[i & 1][j - num[k]];
flag = true; break;
}
else if (w[i] == '?' && j >= num[k]) {
dp[~i & 1][j] += dp[i & 1][j - num[k]];
dp[~i & 1][j] %= mod;
}
}
if (!flag) dp[~i & 1][j] = 0;
}
dp[~i & 1][0] = dp[~i & 1][p], dp[~i & 1][p] = 0;
}
cout << dp[len & 1][0];
return 0;
}
```
:::