1. 程式人生 > 實用技巧 >遞推與遞迴

遞推與遞迴

目錄

遞推與遞迴

1. 演算法分析

1.1 遞推

    遞推強調當前狀態與前一個狀態的關係,一種考察類似動態規劃的思維處理,另一種考察思維:當前狀態確定後,後繼的所有狀態全部確定。

1.2 遞迴

    遞迴的處理思路就算當前狀態取決於子狀態的情況,求當前狀態需要先計算出子狀態後才能決定。

2. 例題

2.1 遞推

2.1.1 思維遞推

acwing95費解的開關
題意: 25盞燈排成一個5x5的方形。每一個燈都有一個開關,遊戲者可以改變它的狀態。每一步,遊戲者可以改變某一個燈的狀態。遊戲者改變一個燈的狀態會產生連鎖反應:和這個燈上下左右相鄰的燈也要相應地改變其狀態。用數字“1”表示一盞開著的燈,用數字“0”表示關著的燈。給定n次遊戲的初始狀態,編寫程式判斷遊戲者是否可能在6步以內使所有的燈都變亮。\(\sum_{}n\)

<= 500
題解: 通過分析可以知道當前行的狀態取決於下一行的按燈方式,而下一行的按燈方式卻決於當前行的狀態。也就是說一旦當前行的狀態確定了,下一行怎麼按燈也決定了,往後遞推,那麼所有燈的狀態也決定了。因此,只需要遍歷第一行可能出現的情況,然後就可以發現所有的狀態,然後判斷第5行是否全為1即可。
程式碼:

#include <bits/stdc++.h>

using namespace std;

int dx[] = {0, -1, 0, 1, 0}, dy[] = {0, 0, 1, 0, -1};
int n;
char g[10][10], backup[10][10];

// 模擬按鍵
void turn (int x, int y ) {
    for (int i = 0; i < 5; ++i) {
        int nx = x + dx[i], ny = y + dy[i];
        if (nx < 0 || nx >= 5 || ny < 0 || ny >= 5) continue;
        g[nx][ny] ^= 1;
    }
    return;
}

int main() {
    cin >> n;
    while (n--) {
        for (int i = 0; i < 5; ++i) scanf("%s", g[i]);
        int ans = 100000;
        for (int op = 0; op < 32; ++op) {  // 遍歷第一行的按燈狀態
            memcpy(backup, g, sizeof g);
            int step = 0;
            for (int i = 0; i < 5; ++i) if (op >> i & 1) turn(0, i), step++;  // 按下第一行的燈
            
            for (int i = 0; i < 4; ++i) {  // 遍歷整個矩陣,如果當前行的第j個位置為0,那麼按下下一行的對應位置
                for (int j = 0; j < 5; ++j) {
                    if (g[i][j] == '0') turn(i + 1, j), step++;
                }
            }
            
            // 判斷最後一行是否全為1
            bool is_success = true;
            for (int i = 0; i < 5; ++i) 
                if (g[4][i] == '0') {
                    is_success = false;
                    break;
                }
            
            // 全為1,則更新答案
            if (is_success) ans = min(ans, step);
            memcpy(g, backup, sizeof backup);
        }
        if (ans <= 6) cout << ans << endl;
        else cout << -1 << endl;
    }
    return 0;
}

Codeforces Round #658 (Div. 2) B. Sequential Nim
題意: 兩個人玩博弈論遊戲,有許多堆石頭,每堆石頭的數目都大於等於1,每次只能從第一堆石頭取,可以取任意個正整數的石頭,一旦不能取石頭那麼就輸了。一開始第一個人先取,如果第一個人最後能贏,輸出First;如果第二個人最後能贏,輸出Second。\(\sum_{}n\) <= 10^5^
題解: 如果當前石頭數大於1,那麼當前這個人可以選擇是否交換先後手;如果當前石頭數位1,那麼當前這個人必須交換先後手。因此,一旦出現第一個大於n的石頭數目,當前這個人就可以根據後面的石頭數,找到最優策略,然後取勝(當前如果是k>1,那麼可以根據後面的石頭確定一種獲勝狀態),因此只需要計算出現第一個大於1的石頭前面1的個數,如果是奇數,那麼Second,否則First(奇數說明到Second拿這堆石頭);如果全為1,那麼奇數個1輸出First,否則Second(奇數說明一直交換先後手,然後又回到First)
程式碼

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
int T, a[N];
int n;

int main() {
    cin >> T;
    while (T--) {
        cin >> n;
        for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
        int k = 1, cnt = 0;
        while (a[k] == 1 && k <= n) cnt++, k++;
        if (cnt == n) cout << (cnt & 1? "First": "Second") << endl;
        else cout << (cnt & 1? "Second": "First") << endl;
    }
    return 0;
}

2.2.4 思維最小步數

acwing1208. 翻硬幣
題意: 一開始給定兩個字串a和b,字串由'*'和'o'組成。每次變化可以將相鄰的兩個字元同時變化(*->0, 0->*),問最少多少次變化可以將a變成b?字串a、b的長度均小於等於100.
題解: 題目咋一看是最小步數模型,但是n能到達100,bfs最小步數必然超時。考慮每次變化的關係:每次變化將相鄰的兩個字元反轉,相當於異或操作。對於異或操作來說,異或奇數次可以規約到1次,異或偶數次可以規約到0次,同時異或沒有先後順序之分。因此,對於所有的變化來說,相當於有n個開關,每個開關控制a[i]和a[i+1]。每個開關只有按和不按兩種情況,要求最小的步數,只需要奇數次都規約到1次,偶數次都規約到0次,然後按下的開關是必須的即可。因此,從前往後掃描,如果a[i]!=b[i],那麼需要按下開關i,然後把a[i]和a[i+1]反轉,這樣每次按下都是必須的,也就是最少的步數。
程式碼:

#include <bits/stdc++.h>

using namespace std;

string s, e;

void turn(int x) {
    if (s[x] == '*') s[x] = 'o';
    else s[x] = '*';
}

int main() {
    cin >> s >> e;
    int res = 0;
    for (int i = 0; i < s.size() - 1; ++i) {
        if (s[i] != e[i]) res++, turn(i), turn(i + 1);
    }
    cout << res << endl;
    return 0;
}

2.1.2 dp類遞推

2.2 遞迴

acwing 92遞迴實現指數型列舉
題意: 從 1 ~ n 這 n 個整數中隨機選取任意多個,輸出所有可能的選擇方案。1 ≤ n ≤ 15
題解: n只有15,使用二進位制表示每個數字是否被選即可
程式碼:

#include <bits/stdc++.h>

using namespace std;

int n;

void dfs(int cur, int num) {
    if (cur == n) {
        for (int i = 0; i < n; ++i) {
            if (num & (1 << i)) cout << i + 1 << " ";
        }
        cout << endl;
        return;
    }
    dfs(cur + 1, num << 1);
    dfs(cur + 1, num << 1 | 1);
    return;
}

int main() {
    cin >> n;
    dfs(0, 0);
    return 0;
}