# SCIST S4 演算法課程_遞迴
:::info
時間:03/17 9:00 ~ 18:00
主題:遞迴
直播連結:https://www.youtube.com/watch?v=3LS6TaUL63M
:::
## 課程內容
- 遞迴
- 尋找相似子結構
- Top-down
- 快速冪
- 講義連結:https://hackmd.io/@sa072686/cp/%2F%40sa072686%2FrJpY0i7PS
- 簡報連結:https://slides.com/sa072686/scist240303_basicdatastructure/fullscreen
# 題解
[TOC]
## [UVa 10696](http://domen111.github.io/UVa-Easy-Viewer/?10696)
撰寫者:fishhh
遞迴的入門題,根據題目的意思以遞迴的方式呈現。
:::spoiler
```cpp=
#include<iostream>
using namespace std;
int f(int n){
if(n>=101){
return n-10;
}
else{
return f(f(n+11));
}
}
int main(){
int n;
while(cin>>n){
if(n==0){
return 0;
}
printf("f91(%d) = %d\n",n,f(n));
}
}
```
:::
## [AtCoder typical90_bq](https://hackmd.io/@sa072686/AtCoder_typical90_bq)
撰寫者:Eason
題目可以理解為:相鄰一格或兩格皆不可同色,求塗滿 $n$ 格方塊的方法數。
~~根據大家應該都學過的排列組合~~,我們將排好的方塊由左而右上色,第一格有 $k$ 種選擇,第二格因為要扣除第一格用過的顏色,所以剩下 $k-1$ 種選擇,第三格要扣除前面兩格用過的顏色,所以剩下 $k-2$ 種選擇,而後每一格都跟第三格一樣,所以都是 $k-2$ 種選擇,根據乘法原理,我們要把所有的選擇數相乘,總方法數就是:
$$
k \times (k-1) \times (k-2)^{n-2}
$$
因為這題的 $n$ 超大,所以次方的部分要用快速冪來求。
:::spoiler code
$$
solve(n)=
\begin{cases}
1 & n=0\\
k-2 & n=1\\
(solve({\frac{n}{2}}))^2 \times (k-2) & \text{n is odd}\\
(solve({\frac{n}{2}}))^2 & \text{n is even}
\end{cases}
$$
```cpp=
#include<bits/stdc++.h>
#define ll long long
#define weakson ios::sync_with_stdio(0), cin.tie(0);
using namespace std;
ll n, k;
ll ans;
const ll MOD = 1e9+7;
ll solve(ll x){
if (x == 0) return 1;
else if (x == 1) return k - 2;
else{
ll tmp = solve (x / 2);
tmp *= tmp;
tmp %= MOD;
if (x & 1){
tmp *= k - 2;
tmp %= MOD;
return tmp;
}
else{
return tmp;
}
}
}
int main(){
weakson;
cin >> n >> k;
if (n == 1) ans = k;
else ans = (((solve (n - 2) * k) % MOD) * (k - 1)) % MOD;
cout << ans << '\n';
return 0;
}
```
:::
## [ZJ f638](https://zerojudge.tw/ShowProblem?problemid=f638)
撰寫者:小麥
**超級細節請小心**
要處理的第一個點是切點左右兩邊的相差值的計算,這點應該要可以想到兩次前綴合這件事,簡單來說就是對一個前綴合再做一次前綴合,就可以算出題目要求的乘積,之後再邊相差。
之後就遞迴,按題意實作
:::spoiler 花了一天的程式碼
```cpp=
#include <bits/stdc++.h>
#define fastio ios::sync_with_stdio; cin.tie(0); cout.tie(0);
#define int long long
using namespace std;
typedef pair<int, int> pii;
typedef vector<int> vi;
typedef vector<vi> v2i;
typedef vector<v2i> v3i;
typedef vector<string> vs;
typedef vector<vs> v2s;
typedef vector<pii> vp;
typedef vector<vp> v2p;
typedef vector<bool> vb;
typedef vector<vb> v2b;
int n, m;
vi arr;
int f(int l, int r, int depth) {
if (depth == m) {
return 0;
}
if (r - l < 3) {
return 0;
}
vi prefix(r - l + 5, 0);
vi suffix(r - l + 5, 0);
for (int i = 1; i <= r - l; i++) {
prefix[i] = prefix[i - 1] + arr[i + l - 1];
}
for (int i = r - l - 1; i >= 0; i--) {
suffix[i] = suffix[i + 1] + arr[i + l];
}
vi prefix2(r - l + 1, 0);
vi suffix2(r - l + 1, 0);
for (int i = 1; i <= r - l; i++) {
prefix2[i] = prefix2[i - 1] + prefix[i];
}
for (int i = r - l - 1; i >= 0; i--) {
suffix2[i] = suffix2[i + 1] + suffix[i + 1];
}
int index = 0;
int minimum = 1e18;
for (int i = l + 1; i < r - 1; i++) {
int diff = abs(prefix2[i - l] - suffix2[i - l]);
if (minimum > diff) {
minimum = diff;
index = i;
}
}
return arr[index] + f(l, index, depth + 1) + f(index + 1, r, depth + 1);
}
void solve() {
while (cin >> n >> m) {
arr.resize(n + 5);
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
cout << f(0, n, 0) << '\n';
}
}
signed main() {
fastio;
solve();
return 0;
}
```
:::
## [Kattis sylvester](https://open.kattis.com/problems/sylvester)
撰寫者:fishhh
文字說明待補
:::spoiler 參考程式
```cpp=
int rec(int now_scale,int x,int y){
if(now_scale==-1)return 1;
int res = 1;
if(x>(1<<now_scale)&&y>(1<<now_scale)){
res = -1;
}
if(x>(1<<now_scale))x-=(1<<now_scale);
if(y>(1<<now_scale))y-=(1<<now_scale);
return rec(now_scale-1,x,y)*res;
}
void solve(){
int n,x,y,h,w;
cin>>n>>x>>y>>h>>w;
int nn = n;
int cnt = 0;
while(nn){
nn>>=1;
cnt++;
}
// cout<<cnt<<"!\n";
x++,y++;
for(int i=0;i<w;i++){
for(int j=0;j<h;j++){
cout<<rec(cnt-2,y+i,x+j)<<" "; // 枚舉 wxh 裡面的每一個點的座標
}
cout<<"\n";
}
cout<<"\n";
}
```
:::
## [ZJ f637](https://zerojudge.tw/ShowProblem?problemid=f637)
## [ZJ f640](https://zerojudge.tw/ShowProblem?problemid=f640)
## [Kattis batmanacci](https://open.kattis.com/problems/batmanacci)
撰寫者:高睿哥哥 (本人:??
題目的作法其實跟遞迴算費波那契數列很像,$S_n$的長度會合$fib_n$相同。
這樣的複雜度聽起來會很糟,因為剛上課說過費波那契數值差不多是$2^n$級別。但其實題目只要求判斷某個字元是什麼。
因為每個$S_n$,其左邊是$S_{n-2}$,右邊是$S_{n-1}$,所以對於$S_n$的第$k$個字元,我們只需判斷它是從$S_{n-1}$或$S_{n-2}$來,非來源的那一邊就不需要計算。
但要如何判斷來源呢? 方法其實就是去看$S_{n-2}$的長度,這樣對於一個詢問$Q(n,k)$,我們就能從$Q(n-2)$和$Q(k-len(S_{n-2}))$中找出其來源。
另外一個要注意的陷阱是,由於費波那契數的值(等同$S_n$的長度)為$2^n$級別,所以$len(S_{n-2})$,要特別判斷會不會超過當前k值(或者不要這麼麻煩,可以向下方code一樣給一個顯然的上界),假設發現會大於該上界,就可以直接預設其來源為$S_{n-2}的部分$
:::spoiler
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mx = 1e18,MAX_N=1e5+5;
vector<int> fb;
char f(int n,int k){
if(n==1) return 'N';
if(n==2) return 'A';
if(n-2>=fb.size()) return f(n-2,k);
int x=fb[n-2];
if(x!=0&&k>x) return f(n-1,k-x); //右半了
return f(n-2,k); //左半
}
void solve(){
int n,k;
cin >> n >> k;
fb.push_back(0);
fb.push_back(1);
for(int i=1;;i++){
int x=fb[i]+fb[i-1];
if(x>mx) break;
fb.push_back(x);
}
cout << f(n,k);
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0);
int t=1;
// cin >> t;
while(t--){
solve();
}
}
```
:::
## [UVa 271](http://domen111.github.io/UVa-Easy-Viewer/?271)
## [Kattis hypercube](https://open.kattis.com/problems/hypercube)
## [CSES 2165](https://cses.fi/problemset/task/2165/)
撰寫者:Paul
~~相信大家都知道河內塔~~,簡單來說就是有 $n$ 個圓盤和三根桿子,一開始$n$個圓盤由大到小,由下到上放在第一根桿子。目標是將所有圓盤由大到小,由下到上放在第三根桿子,但是一次只能動一個在桿子最上方圓盤,而且每次移動都要保持由大到小,由下到上排放。
可以先假設三根桿子分別為目前所在的桿子、目標桿子和空的桿子,如果 $n = 1$ 就直接移過去較好了。其他的情形可以想成:先將 $n - 1$ 個圓盤從目前位置移到空桿,將第 $n$ 個圓盤從目前位置移到目標桿,最後再將 $n - 1$ 個圓盤從空桿移到目標桿,就可以完成目標。
至於移動的次數,假設有 $n$ 個圓盤時的總移動次數為 $f(n)$ ,則: $f(1) = 1$, $f(n) = 2*f(n-1) + 1$ 。可以證明 $f(n) = 2^n - 1$。
:::spoiler
```cpp=
#include<bits/stdc++.h>
using namespace std;
void f(int num, int now, int goal, int empty)
{
if(num==1) cout<<now<<' '<<goal<<endl;
else
{
f(num-1, now, empty, goal);
cout<<now<<' '<<goal<<endl;
f(num-1, empty, goal, now);
}
}
int main()
{
int n, ans=1;
cin>>n;
for(int i=1;i<=n;i++)
{
ans*=2;
}
cout<<ans-1<<endl;
f(n, 1, 3, 2);
}
```
:::
## [ZJ c296](https://zerojudge.tw/ShowProblem?problemid=c296)
----
## 預處理
## [AtCoder typical90_d](https://hackmd.io/@sa072686/AtCoder_typical90_d)
## [AtCoder typical90_at](https://hackmd.io/@sa072686/AtCoder_typical90_at)
## [TOJ 120](https://toj.tfcis.org/oj/pro/120/)
撰寫:fishhh
前綴和裸題。
:::spoiler 參考程式
```cpp=
#include<iostream>
using namespace std;
int main(){
long long t,tree[300000],n,a,b;
cin>>t;
tree[0]=0;
for(int i=1;i<=t;i++)cin>>tree[i],tree[i]+=tree[i-1];
cin>>n;
for(int i=0;i<n;i++){
cin>>a>>b;
if(a>b)swap(a,b);
cout<<tree[b]-tree[a-1]<<"\n";
}
}
```
:::
## [AtCoder abc048_b](https://atcoder.jp/contests/abc048/tasks/abc048_b)
## [CSES 1662](https://cses.fi/problemset/task/1662/)
## [Kattis commercials](https://open.kattis.com/problems/commercials)