owned this note
owned this note
Published
Linked with GitHub
# 111學年度上學期進階班期末考題解
## 哥德巴赫猜想(偽)
`tag: prime number, 梗題`
給你一數$N$
把$N$拆成多個質數相加
---
不知道哪個時候想到的題目
反正當時想到就想出成梗題
---
### $1\%:\ N \in prime\ number$
既然$N$自己就是質數
那就輸出他就好了
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
LL N;
cin >> N;
cout << N << endl << 1 << endl;
}
}
```
:::
`Time Complexity:` $O(T)$
`Space Complexity:` $O(1)$
---
### $14\%:\ 1 < N \leq 20$
可以直接把 $2 \sim 20$ 的答案列出來
| $N$ | $ans$ | $N$ | $ans$ |
| ---- | ------ | ---- | ------ |
| $2$ | $2*1$ | $12$ | $2*6$ |
| $3$ | $3*1$ | $13$ | $13*1$ |
| $4$ | $2*2$ | $14$ | $2*7$ |
| $5$ | $5*1$ | $15$ | $3*5$ |
| $6$ | $2*3$ | $16$ | $2*8$ |
| $7$ | $7*1$ | $17$ | $17*1$ |
| $8$ | $2*4$ | $18$ | $2*9$ |
| $9$ | $3*3$ | $19$ | $19*1$ |
| $10$ | $2*5$ | $20$ | $2*10$ |
| $11$ | $11*1$ | | |
然後套一堆`if`就可以了
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
LL N;
cin >> N;
if(N == 2) {
cout << 2 << endl << 1 << endl;
}
if(N == 3) {
cout << 3 << endl << 1 << endl;
}
if(N == 4) {
cout << 2 << endl << 2 << endl;
}
if(N == 5) {
cout << 5 << endl << 1 << endl;
}
if(N == 6) {
cout << 3 << endl << 2 << endl;
}
if(N == 7) {
cout << 7 << endl << 1 << endl;
}
if(N == 8) {
cout << 2 << endl << 4 << endl;
}
if(N == 9) {
cout << 3 << endl << 3 << endl;
}
if(N == 10) {
cout << 2 << endl << 5 << endl;
}
if(N == 11) {
cout << 11 << endl << 1 << endl;
}
if(N == 12) {
cout << 2 << endl << 6 << endl;
}
if(N == 13) {
cout << 13 << endl << 1 << endl;
}
if(N == 14) {
cout << 2 << endl << 7 << endl;
}
if(N == 15) {
cout << 3 << endl << 5 << endl;
}
if(N == 16) {
cout << 2 << endl << 8 << endl;
}
if(N == 17) {
cout << 17 << endl << 1 << endl;
}
if(N == 18) {
cout << 2 << endl << 9 << endl;
}
if(N == 19) {
cout << 19 << endl << 1 << endl;
}
if(N == 20) {
cout << 2 << endl << 10 << endl;
}
}
}
```
:::
`Time Complexity:` $O(T)$
`Space Complexity:` $O(1)$
---
### $20\%:\ 1 < N \leq 10^4,\ N \in even\ number$
從題目可以得知,較小的偶數可以確定符合這個猜想
所以在這個測資點,可以把$N$拆成兩個質數(除了$N=2$)
記得要特判$N=2$
而要怎麼去確定一個數字是否為質數呢
```cpp=
inline bool is_prime(LL n) {
for(LL i = 2; i * i <= n; i ++ ) {
if(n % i == 0) {
return 0;
}
}
return 1;
}
```
這個方法可以$O(\sqrt{n})$算出$n$是否為質數
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
inline bool is_prime(LL n) {
for(LL i = 2; i * i <= n; i ++ ) {
if(n % i == 0) {
return 0;
}
}
return 1;
}
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
LL N;
cin >> N;
if(N == 2) {
cout << 2 << endl << 1 << endl;
continue;
}
for(LL i = 2; i < N; i ++ ) {
LL a = i, b = N - i;
if(is_prime(a) && is_prime(b)) {
cout << a << " " << b << endl;
cout << 1 << " " << 1 << endl;
break;
}
}
}
}
```
:::
`Time Complexity:` $O(TN\sqrt{N})$
`Space Complexity:` $O(1)$
---
### $(1\%) + 14\% + 20\% + 25\% = 60\% :\ 1 < N \leq 10^4$
一個數如果不是偶數,那他就是奇數(廢話)
那我們可以把$N$分成兩種情況來看:
$N$為偶數:
直接代$20\%$的code
$N$為奇數:
那有一個$N$為奇數的話,可以寫成$3 + (N - 3)$
且$3$為質數,$N - 3$為偶數
接著就可以用$N - 3$去代$15\%$的code(除了$N=3 or N=5$)
記得$N=2\ or\ N=3\ or\ N=5$時要特判
這樣就可以同時解掉$9\%,\ 15\%$和$35\%$的側資點
照理來說$1\%$的側資點不會被解掉
只是因為我把那個側資點的$T$設有夠小($20$)
加上你只要去判他是不是質數就能拿到這$1\%$了
所以我分數就直接送下去了
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
inline bool is_prime(LL n) {
for(LL i = 2; i * i <= n; i ++ ) {
if(n % i == 0) {
return 0;
}
}
return 1;
}
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
LL N;
cin >> N;
if(N == 2) {
cout << 2 << endl << 1 << endl;
continue;
}
if(N == 3) {
cout << 3 << endl << 1 << endl;
continue;
}
if(N == 5) {
cout << 5 << endl << 1 << endl;
continue;
}
bool odd = 0;
if(N % 2 == 1) {
odd = 1;
N -= 3;
}
for(LL i = 2; i < N; i ++ ) {
LL a = i, b = N - i;
if(is_prime(a) && is_prime(b)) {
if(odd)
cout << 3 << " ";
cout << a << " " << b << endl;
if(odd)
cout << 1 << " ";
cout << 1 << " " << 1 << endl;
break;
}
}
}
}
```
:::
`Time Complexity:` $O(TN\sqrt{N})$
`Space Complexity:` $O(1)$
---
### $1\% + 14\% + 20\% + 25\% + 40\% = 100\% :\ 1 < N \leq 2^{31} - 1$
定理:當有一個$N\ge 2$時,可以寫成$2m+3n(m, n \in \mathbb{Z^+})$
證明:
一數$N$可以被分成兩種狀態,奇數or偶數
當$N$為偶數:
$N$可以被寫成$2a$,則$m=a,\ n=0$
當$N$為奇數:
$N$可以被寫成$2a+1=2(a-1)+3$,則$m=a-1,\ n=1$
$\mathbb{Q.E.D.}$
因為$n=0$在題目的輸出中是合法的
所以也不用特判,直接輸出即可
這就是這題梗的地方
(而且程式碼有夠簡單)
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
LL N;
cin >> N;
if(N % 2) {
cout << 2 << " " << 3 << endl;
cout << N / 2 - 1 << " " << 1 << endl;
}
else {
cout << 2 << endl;
cout << N / 2 << endl;
}
}
}
```
:::
`Time Complexity:` $O(T)$
`Space Complexity:` $O(1)$
---
---
## 兩元三元
`tag: quadratic equation, 成年禮`
有兩元,有三元
一個兩元 + 一個三元 = 一個七元
給你一個數字$N$
有兩元$m$個跟三元$n$個
將這堆錢的幣值最大化
求有幾種不重複的$(m, n)$,可以得到$N$的總價錢
---
高二成年禮玩大地遊戲的時候想到的題目
想了想發現還滿水的
---
### $5\% :\ 1 \leq N \leq 10$
直接把答案列出來就可以了
| N | ans | N | ans |
| --- | --- | ---- | --- |
| $1$ | $0$ | $6$ | $2$ |
| $2$ | $1$ | $7$ | $1$ |
| $3$ | $1$ | $8$ | $1$ |
| $4$ | $1$ | $9$ | $2$ |
| $5$ | $0$ | $10$ | $2$ |
然後用陣列就可以了
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
int main() {
// IO;
LL T;
cin >> T;
LL arr[10] = {0, 1, 1, 1, 0, 2, 1, 1, 2, 2};
while(T -- ) {
LL N;
cin >> N;
cout << arr[N - 1] << endl;
}
}
```
:::
`Time Complexity:` $O(T)$
`Space Complexity:` $O(N)$
---
### $5\% + 20\% = 25\% :\ 1 \leq N \leq 10^3$
可以去模擬有幾個兩元跟三元
用雙層迴圈去模擬
當還沒氧化前的總和就$>N$就可以跳下一次迴圈
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
LL N;
cin >> N;
LL ans = 0;
for(LL i = 0; ; i ++ ) {
if(i * 2 > N)
break;
for(LL j = 0; ; j ++ ) {
if(i * 2 + j * 3 > N)
break;
LL tmp = min(i, j);
LL num = tmp * 7 + (i - tmp) * 2 + (j - tmp) * 3;
if(num == N)
ans ++ ;
}
}
cout << ans << endl;
}
}
```
:::
`Time Complexity:` $O(TN^2)$
`Space Complexity:` $O(1)$
---
### $5\% + 20\% + 35\% = 60\% :\ 1 \leq N \leq 10^6$
題目說要將幣值最大化
也就是要將能換成七元的錢都換成七元
當我們這樣做時
我們會發現最後沒有被換成七元的一定是兩元或三元其中一種(或是都沒剩)
那這樣題目就可以另寫成:
求$N = 7m+2n\ or\ N = 7m+3n$的$(m, n)$有幾組不重複解$(m, n \in \mathbb{Z^+})$
那我們可以去對$m$做窮舉,找$n$的可能情況
但要注意一件事
當$n = 0$時,$(m, n)$會被同時算在$7m+2n\ \&\ 7m+3n$中
所以這時候的答案要$-1$
那什麼時候$n$會出現$=0$的情況呢
就是當$N = 0\ (mod\ 7)$的時候
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
LL N;
cin >> N;
LL ans = 0;
for(LL i = N; i >= 0; i -= 7) {
ans += (i % 2 == 0);
ans += (i % 3 == 0);
}
if(N % 7 == 0)
ans -- ;
cout << ans << endl;
}
}
```
:::
`Time Complexity:` $O(TN)$
`Space Complexity:` $O(1)$
---
### $5\% + 20\% + 35\% + 40\% = 100\% :\ 1 \leq N \leq 2^{31} - 1$
要想 <b style="color: lime;">AC</b> 就要動用到一點數學常識了
先以$7m+2n=N$為例
$7m+2n = 7(m-2)+2(n+7) = 7(m-4)+2(n+14)$
$=\cdots=7(m-2k) + 2(n+7k)\ (k \in \mathbb{Z^+})$
然後因為$(m, n \in \mathbb{Z^+})$,所以要滿足$(m-2k) \ge 0$
我們可以先找到$m$最大的解,然後找有幾組的$(m-2k) \ge 0$,也就是$m/2+1$組
先以$7m+3n=N$也是一樣的道理
然後記得當$N = 0\ (mod\ 7)$的時候要把答案$-1$
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
LL N;
cin >> N;
LL ans = 0, a, b;
a = N / 7, b = N % 7;
while(b % 2)
a -- , b += 7;
if(a >= 0)
ans += a / 2 + 1;
a = N / 7, b = N % 7;
while(b % 3)
a -- , b += 7;
if(a >= 0)
ans += a / 3 + 1;
if(N % 7 == 0)
ans -- ;
cout << ans << endl;
}
}
```
:::
`Time Complexity:` $O(T)$
`Space Complexity:` $O(1)$
---
---
## 數之數織
`tag: DFS, pruning`
給你一個數織,完成他
---
當初想說除了數獨跟八皇后還有什麼比較好出的DFS題
然後就想到了這個東西
去查才知道這個鬼東西叫數織
題外話
我這題想了好幾天
一直改想法,一直把題目`debuff`
生測資生到快崩潰
所以我要先在試前做一個大膽的猜測
### <font style="color: red; font-size: 50px">大膽的猜測</font>
這題應該不會有人(除了$frankie$)在競賽中<b style="color: lime;">AC</b>
然後我先猜$frankie$會<font>WA</font>至少$20$次
---
### $1\%:\ N = 1$
因為題目保證測資皆有解
所以很直觀的,直接輸出$1$就可以了
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
LL N, lft, up;
cin >> N >> lft >> up;
cout << 1 << endl;
}
}
```
:::
`Time Complexity:` $O(T)$
`Space Complexity:` $O(1)$
---
### $14\% :\ N = 2$
因為題目保證測資皆有解
那在$N=2$的情況下只會有$1$跟$2$這兩種答案
然後$ans=2$的情況也就只有$1\ 1\ 1\ 1$這種
其他都是$ans=1$
然後如果你想要那$1\%$也可以再判$N=1$
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
LL N, a, b, c, d;
cin >> N >> a >> b >> c >> d;
if(a*b*c*d == 1)
cout << 2 << endl;
else
cout << 1 << endl;
}
}
```
:::
`Time Complexity:` $O(T)$
`Space Complexity:` $O(1)$
---
### $1\% + 14\% + 45\% = 60\% :\ N \leq 5$
就純$DFS$
但因為題目有限制格中只有一個數字
所以不用一格一格去塗黑
可以先由同一個方向塗$N$條黑格
然後塗完再用另一個方向去檢驗這個解法是否可行
這個實作可以用一個二維$bool$陣列來做
有塗黑就$1$,反之則$0$
然後記得把變數開在全域
喔然後格中有可能會出現$0$這個數字
為了避免重複計算答案
遇到$0$就直接跳下一條黑格來塗
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
constexpr LL maxN = 15;
LL N, ans;
LL lft[maxN], up[maxN];
bool mat[maxN][maxN];
inline void DFS(LL pos = 0) {
if(pos == N) {
for(LL i = 0; i < N; i ++ ) {
LL cnt = 0, tmp = 0;
// tmp: 0 = not yet in tile, 1 = in tile, 2 = over tile
for(LL j = 0; j < N; j ++ ) {
if(tmp == 0 && mat[j][i])
tmp ++ ;
if(tmp == 1 && !mat[j][i])
tmp ++ ;
if(mat[j][i] && tmp == 2)
return;
cnt += mat[j][i];
}
if(cnt != up[i])
return;
}
ans ++ ;
return;
}
if(lft[pos] == 0) {
DFS(pos + 1);
return;
}
for(LL i = 0; i + lft[pos] - 1 < N; i ++ ) {
for(LL j = i; j < i + lft[pos]; j ++ )
mat[pos][j] = 1;
DFS(pos + 1);
for(LL j = i; j < i + lft[pos]; j ++ )
mat[pos][j] = 0;
}
}
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
ans = 0;
memset(mat, 0, sizeof(mat));
cin >> N;
for(LL i = 0; i < N; i ++ )
cin >> lft[i];
for(LL i= 0; i < N; i ++ )
cin >> up[i];
DFS();
cout << ans << endl;
}
}
```
:::
`Time Complexity:` $O(TN^2)$
`Space Complexity:` $O(N^2)$
---
### $1\% + 14\% + 45\% + 40\% = 100\% :\ N \leq 10$
這題如果真的要優化的話
肯定有很多種方法
反正我這邊丟我的方法
跟$60\%$一樣,先對一個方向塗黑條
但跟$60\%$不一樣的地方在於
要一邊塗一邊用另一個方向去判斷解法是否可行
大概優化的想法在這邊
細節的話我也講一下好了
除了$lft$跟$up$,還要設一個檢查陣列$chk$跟一個計數陣列$arr$
其中$chk$是來表示「第$i$欄是否還可以被放置黑格」
並且其中的元素分成三種值:
$0:$都行,$1:$這欄的下一格一定要塗黑,$-1:$這欄不能再被塗黑
$chk$是來表示「第$i$欄被放了幾個黑格」
預設都為$0$
所以這題其實可以不用二為陣列去實作
接下來只要一邊塗黑條一邊去動態看這樣塗是否合法
就可以完成了
然後注意一些細節($0$的出現,有些欄沒被塗完,$DFS$回上層要把資料回歸 $\cdots$)
大概就能<b style="color: lime;">AC</b>了
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
#define mem(x) memset(x, 0, sizeof(x))
using namespace std;
using LL = long long;
constexpr LL maxN = 15;
LL N;
LL up[maxN], lft[maxN], arr[maxN], chk[maxN], ans = 0;
// chk[i] : 1 = put, 0 = all ok, -1 = don't put
inline void DFS(LL pos = 0) {
if(pos == N) {
for(LL i = 0; i < N; i ++ ) {
if(arr[i] != up[i])
return;
}
ans ++ ;
return;
}
if(lft[pos] == 0) {
for(LL i = 0; i < N; i ++ ) {
if(chk[i] == 1)
return;
}
DFS(pos + 1);
return;
}
for(LL i = 0; i + lft[pos] - 1 < N; i ++ ) {
bool bln = 1;
for(LL j = 0; j < N; j ++ ) {
if(i <= j && j <= i + lft[pos] - 1) {
if(chk[j] < 0) {
bln = 0;
break;
}
}
else if(chk[j] > 0) {
bln = 0;
break;
}
}
if(!bln)
continue;
for(LL j = i; j < i + lft[pos]; j ++ ) {
arr[j] ++ ;
if(arr[j] == up[j])
chk[j] = -1;
else
chk[j] = 1;
}
DFS(pos + 1);
for(LL j = i; j < i + lft[pos]; j ++ ) {
arr[j] -- ;
if(arr[j])
chk[j] = 1;
else
chk[j] = 0;
}
}
}
inline void init() {
mem(up);
mem(lft);
mem(arr);
mem(chk);
ans = 0;
}
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
init();
cin >> N;
for(LL i = 0; i < N; i ++ )
cin >> lft[i];
for(LL i = 0; i < N; i ++ )
cin >> up[i];
DFS();
cout << ans << endl;
}
}
```
:::
`Time Complexity:` $O(TN^2)$
`Space Complexity:` $O(N)$
---
---
## 長方形分配者—小駿
`tag: recursion, DP, matrix exponentiation, 小駿`
有一個寬為$1$且長為$N$的矩形
從這個矩形的長邊其中一端,一直切下「長為整數且大於1的長方形」,而最後不剩
有多少切法
---
在寫$110$學科時想到的題目
是當時$pI$的簡化但又加強版
---
### $5\%:\ 1 \leq N \leq 5$
直接列出來
| N | ans |
| --- | --- |
| $1$ | $0$ |
| $2$ | $1$ |
| $3$ | $1$ |
| $4$ | $2$ |
| $5$ | $3$ |
然後用陣列
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
int main() {
// IO;
LL T;
cin >> T;
LL arr[5] = {0, 1, 1, 2, 3};
while(T -- ) {
LL N;
cin >> N;
cout << arr[N - 1] << endl;
}
}
```
:::
`Time Complexity:` $O(T)$
`Space Complexity:` $O(N)$
---
### $5\% + 20\% = 25\%:\ 1 \leq N \leq 30$
直接用遞迴模擬切掉的長度
當完整切完就可以回傳$1$
(你要用`if else`我也是沒差啦)
然後$N=1$要特判
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
inline LL ans(LL n) {
if(n == 0)
return 1;
LL r = 0;
for(LL i = 2; i <= n; i ++ )
r += ans(n - i);
return r;
}
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
LL N;
cin >> N;
if(N == 1) {
cout << 0 << endl;
continue;
}
cout << ans(N) << endl;
}
}
```
:::
`Time Complexity:` $O(T*2^N)$
`Space Complexity:` $O(1)$
---
### $5\% + 20\% + 30\% = 55\%:\ 1 \leq N \leq 10^3$
剛剛的`code`可以寫出以下遞迴式:
$\large{f(n) = \begin{cases} 1 \qquad \qquad \qquad \qquad if\ \ n=0 \\ 0 \qquad \qquad \qquad \qquad if\ \ n=1 \\ \Sigma_{k = 2}^{n} f(n-k)\qquad else \\ \end{cases}}$
因為我們可以從一端切下長度為$2 \sim N$的長方形
那這樣我們就可以用陣列存取重複的值
也就是用$DP$
然後從這個側資點開始
答案會超過`long long`範圍
所以別忘了$mod\ 10^9+7$
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
constexpr LL mod = 1e9 + 7;
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
LL N;
cin >> N;
LL arr[N + 1];
memset(arr, 0, sizeof(arr));
arr[0] = 1;
for(LL i = 1; i <= N; i ++ ) {
for(LL j = 2; j <= i; j ++ ) {
arr[i] = (arr[i] + arr[i - j]) % mod;
}
}
cout << arr[N] << endl;
}
}
```
:::
`Time Complexity:` $O(T*N^2)$
`Space Complexity:` $O(N)$
---
### $5\% + 20\% + 30\% + 35\% = 90\%:\ 1 \leq N \leq 10^6$
我們再看一下剛剛的遞迴式:
$\large{f(n) = \begin{cases} 1 \qquad \qquad \qquad \qquad if\ \ n=0 \\ 0 \qquad \qquad \qquad \qquad if\ \ n=1 \\ \Sigma_{k = 2}^{n} f(n-k)\qquad else \\ \end{cases}}$
其中我們看到`else`那邊
$\large{f(n) = \Sigma_{k = 2}^{n} f(n-k)}$
我們做一個神奇的操作,先列下這兩條式子
$f(n) \quad\;\;\, = f(n-2)+f(n-3)+f(n-4)+\cdots+f(2)+f(1)+f(0)$
$f(n-1) = \qquad\qquad\;\;\; f(n-3)+f(n-4)+\cdots+f(2)+f(1)+f(0)$
我把兩式相減得
$f(n) - f(n - 1) = f(n - 2)$
再移個項
$f(n) = f(n - 1) + f(n - 2)$
太神奇了,這不就是費氏數列嗎
只是要記得$f(0) = 1 \ \&\ f(1) = 0$
(其實只要把前幾項列出來就能大概猜到了)
就直接給他$DP$砸下去
要建表或不建表都過得了所以沒差
要滾動或不滾動都過得了所以沒差
只是如果不滾動就要用$vector$,陣列會爆
別忘了$mod\ 10^9+7$
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
constexpr LL mod = 1e9 + 7;
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
LL N;
cin >> N;
vector<LL> vec(N + 1, 0);
vec[0] = 1;
for(LL i = 2; i <= N; i ++ )
vec[i] = (vec[i - 1] + vec[i - 2]) % mod;
cout << vec[N] << endl;
}
}
```
:::
`Time Complexity:` $O(TN)$
`Space Complexity:` $O(N)$
---
### $\color{red}{5\% + 20\% + 30\% + 35\% + 10\% = 100\%:\ 1 \leq N \leq 2^{31}-1}$
為什麼上面那個那麼紅勒
因為這個側資點大概只有`frankie`解得出來
應該可以說是這次的防破台側資點
先給大家科普一下「矩陣快速冪」是什麼
把遞迴式寫成矩陣乘法式
之後藉由快速冪將矩陣乘法加速
最後省下一大堆時間的$DP$優化
整體可以看去年講義的[這個](https://hackmd.io/@fdhscpp110/matix_fast_pow)
反正這個側資點就是必須用這個優化才能過去
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
template<typename T> using Vec = vector<T>;
template<typename T> using Mat = Vec<Vec<T> >;
constexpr LL mod = 1e9 + 7;
inline Mat<LL> multi(Mat<LL> a, Mat<LL> b, LL x, LL y, LL z) {
Mat<LL> ret(x, Vec<LL>(y, 0));
for(LL i = 0; i < x; i ++ ) {
for(LL j = 0; j < y; j ++ ) {
for(LL k = 0; k < z; k ++ ) {
ret[i][j] = (ret[i][j] + a[i][k] * b[k][j]) % mod;
}
}
}
return ret;
}
inline Mat<LL> pwr(Mat<LL> a, LL b) {
Mat<LL> r = {{1, 0}, {0, 1}};
for(; b; a = multi(a, a, 2, 2, 2), b >>= 1) {
if(b & 1)
r = multi(r, a, 2, 2, 2);
}
return r;
}
inline LL Fib(LL n) {
Mat<LL> r = {{1}, {1}}, a = {{1, 1}, {1, 0}};
r = multi(pwr(a, n), r, 2, 1, 2);
return r[0][0];
}
int main() {
// IO;
LL T;
cin >> T;
while(T -- ) {
LL N;
cin >> N;
if(N == 1) {
cout << 0 << endl;
continue;
}
if(N == 2) {
cout << 1 << endl;
continue;
}
cout << Fib(N - 3) << endl;
}
}
```
:::
`Time Complexity:` $O(TlgN)$
`Space Complexity:` $O(1)$
---
---
## 更大堆的0與1
`tag: binary, 型態轉換, 卡記憶體, 11011111101010010`
給你$N$跟$K$
再給$2^N-K$個由$0,\ 1$組成的長度為$N$的不重複字串
輸出$K$個跟這些字串不一樣的「由$0,\ 1$組成的長度為$N$的字串」
---
某天我哥在跟我討論他出爛的某一題
我就在想能不能出個進階版然後讓他至少不是爛的
然後這題就出出來了
---
### $0\%:$ 基本常識 & 注意事項
「由$0,\ 1$組成的長度為$N$的不重複字串」
這個東西總共有$2^N$種
這算是基本常識,也可用排列組合來解釋
反正就是有$N$位,每位有$2$種狀態
所以是$2^N$種
所以題目可以看成
把所有沒列出的狀態列出來
喔然後記得壓$IO$優化
---
### $5\%:\ N = 3,\ K = 1$
$N=3$共有$8$種狀態:
$000,\ 001,\ 010,\ 011,\ 100,\ 101,\ 110,\ 111$
然後可以用`set`維護有哪些還沒出現過
你要用`if`我也是沒差但我懶
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
int main() {
IO;
LL T;
cin >> T;
while(T -- ) {
LL N, K;
cin >> N >> K;
set<string> st = {"000", "001", "010", "011", "100", "101", "110", "111"};
for(LL i = 0; i < 8 - K; i ++ ) {
string str;
cin >> str;
st.erase(str);
}
for(auto &i : st)
cout << i << endl;
}
}
```
:::
`Time Complexity:` $O(TN^22^N)$
`Space Complexity:` $O(2^N)$
---
### $5\% + 15\% = 20\% :\ N \leq 10$
$N=10$共有$1024$種狀態
太多了列不出來
所以這個時候只能用迴圈一個一個生
可以發現這些字串其實就是$0 \sim 2^N-1$的二進位
所以可以寫一個函式把十進位轉為二進位
像這樣
```cpp=
inline string d_to_b(LL num) {
string str;
while(num) {
str += (num & 1) + '0';
num >>= 1;
}
while(str.length() < N)
str += '0';
reverse(str.begin(), str.end());
return str;
}
```
要注意的是要把前面不足的$0$補起來
然後求$2^N$可以用$pow(2,\ N)$這個函式
雖然不太好但因為$N$夠小所以還是準的
一樣用`set`維護有哪些還沒出現過
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
LL N, K;
inline string d_to_b(LL num) {
string str;
while(num) {
str += (num & 1) + '0';
num >>= 1;
}
while(str.length() < N)
str += '0';
reverse(str.begin(), str.end());
return str;
}
int main() {
IO;
LL T;
cin >> T;
while(T -- ) {
cin >> N >> K;
LL M = pow(2, N);
set<string> st;
for(LL i = 0; i < M; i ++ )
st.insert(d_to_b(i));
for(LL i = 0; i < M - K; i ++ ) {
string str;
cin >> str;
st.erase(str);
}
for(auto &i : st)
cout << i << endl;
}
}
```
:::
`Time Complexity:` $O(TN^22^N)$
`Space Complexity:` $O(2^N)$
---
### $5\% + 40\% = 45\%:\ N \leq 18,\ K = 1$
現在不能用`set`了,我有卡記憶體
但可以注意一件事,$K = 1$
這代表剩一個沒有出現
那我們這邊再給一個小常識
「在所有排列中,所有位數出現$0\ or\ 1$的個數都是$2^{N-1}$」
就以$N=3$來說
$000,\ 001,\ 010,\ 011,\ 100,\ 101,\ 110,\ 111$
第一位為$0$的有$4$個,第一位為$1$的有$4$個
第二位為$0$的有$4$個,第二位為$1$的有$4$個
第三位為$0$的有$4$個,第三位為$1$的有$4$個
所以我們可以去記「第$i$位為$0$的有幾個」
是$2^{N-1}$的話答案的第$i$位就是$1$
反之就是$0$
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
int main() {
IO;
LL T;
cin >> T;
while(T -- ) {
LL N, K;
cin >> N >> K;
LL arr[N], M = pow(2, N);
for(LL i = 0; i < M - K; i ++ ) {
string str;
cin >> str;
for(LL i = 0; i < N; i ++ )
if(str[i] == '0')
arr[i] ++ ;
}
for(auto &i : arr)
cout << (i == pow(2, N - 1) ? '1' : '0');
cout << endl;
}
}
```
:::
`Time Complexity:` $O(TN2^N)$
`Space Complexity:` $O(N)$
---
### $5\% + 15\% + 40\% + 40\% = 100\%:\ N \leq 18$
現在不能用`set`了,我有卡記憶體
也不能用$K = 1$的方法了
那我也懶得想怎麼講的比較好懂,因為懂得都懂
所以直接講解法
可以把每個輸入的字串想像成二進位,並把這些轉成十進位
然後用一個長為$2^N$的`bool`陣列去存有那些出現過
最後找沒出現過的然後把十進位轉成二進為後輸出就可以了
大概就是
```cpp=
bool arr[pow(2, N)] = 全都0;
while(bla bla bla) {
cin >> str;
LL num = 二進位轉十進位(str);
arr[num] = 1;
}
for(LL i = 0, i < pow(2, N); i ++ )
if(!arr[i])
cout << 十進位轉二進位(i);
```
所以還要再寫一個函式是二進位轉十進位
```cpp=
inline LL b_to_d(string str) {
LL r = 0;
for(LL i = str.length() - 1, tmp = 1; i >= 0; i -- , tmp *= 2)
if(str[i] == '1')
r += tmp;
return r;
}
```
大概就是這樣
:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define IO cin.tie(0) -> sync_with_stdio(0)
#define endl "\n"
using namespace std;
using LL = long long;
LL N, K;
inline string d_to_b(LL num) {
string str;
while(num) {
str += (num & 1) + '0';
num >>= 1;
}
while(str.length() < N)
str += '0';
reverse(str.begin(), str.end());
return str;
}
inline LL b_to_d(string str) {
LL r = 0;
for(LL i = str.length() - 1, tmp = 1; i >= 0; i -- , tmp *= 2)
if(str[i] == '1')
r += tmp;
return r;
}
int main() {
IO;
LL T;
cin >> T;
while(T -- ) {
cin >> N >> K;
LL M = pow(2, N);
bool arr[M];
memset(arr, 0, sizeof(arr));
for(LL i = 0; i < M - K; i ++ ) {
string str;
cin >> str;
arr[b_to_d(str)] ++ ;
}
for(LL i = 0; i < M; i ++ ) {
if(!arr[i]) {
cout << d_to_b(i) << endl;
}
}
}
}
```
:::
`Time Complexity:` $O(TN2^N)$
`Space Complexity:` $O(2^N)$