1. 程式人生 > 其它 >藍橋杯 第一講 遞迴與遞推

藍橋杯 第一講 遞迴與遞推

92. 遞迴實現指數型列舉

dfs遞迴做法

#include<iostream>
using namespace std;
const int N = 20;

int n;
bool st[N]; //1~N每個數的狀態陣列:0表示未選擇,1表示已選擇
void dfs(int u)
{
    if(u >= n) //遞迴出口:深搜到第n+1位,列印該方案
    {
        for(int i=0;i < n;i++)
        {
            if(st[i])//選擇該數就列印
            {
                cout<<i+1<<" ";
            }
        }
        puts("");
        return;
    }
    
    st[u] = true; //第一個分支:選擇該數
    dfs(u+1);
    st[u] = false; //dfs必須恢復狀態
    dfs(u+1);//第二個分支:不選該數
}
int main()
{
    cin>>n;
    dfs(0);
    return 0;
}

二進位制數位迴圈列舉做法

#include<iostream>
using namespace std;
const int N = 20;

int n;
int a[N] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
int main()
{
    cin>>n;
    for(int i=0;i < 1<<n;i++)
    {
        int t = i;
        for(int j=0;j<n;j++)
        {
            if(t%2 == 1) cout<<a[j]<<" ";
            t = t>>1;
        }
        puts("");
    }
    return 0;
}

94. 遞迴實現排列型列舉


O(n!*n)

#include<iostream>

using namespace std;
const int N = 10;

bool st[N];//1~N中每個數是否被使用
int n;
int a[N];//方案
void dfs(int u)//列舉到第幾位
{
    if(u >= n) //已經列舉完最後一位
    {
        for(int i=0;i<n;i++)
        {
            cout<<a[i]<<" ";//輸出方案
        }
        puts("");
        return;
    }
    for(int i=1;i<=n;i++) //從小到大列舉每個數
    {
        if(!st[i]) //沒有被用過
        {
            st[i] = true; //標記使用
            a[u] = i;//記錄到方案中
            dfs(u+1);//搜尋下一位

            st[i] = false; //下一層搜尋回來,恢復狀態
        }
    }
}
int main()
{
    cin>>n;
    dfs(0);
    return 0;
}

93. 遞迴實現組合型列舉

y總寫法

#include<iostream>

using namespace std;

const int N = 30;
int n,m;
int way[N];

void dfs(int u,int st) //u是第幾位,st是從哪個數開始列舉
{
    if(n - st < m - u) return;//剪枝:當前剩餘待選的數的個數小於空位數
    if(u == m + 1)
    {
        for(int i=1;i<=m;i++)
        {
            cout<<way[i]<<" ";
        }
        puts("");
    } 
    
    way[u] = st;
    dfs(u+1,st+1);
    dfs(u+1,st)
}
int main()
{
    cin>>n>>m;
    dfs(1,1);
}

我的寫法

#include<iostream>

using namespace std;

const int N = 25;
int n,m;
bool st[N];
void dfs(int x,int t) //x表示選擇的數,t表示已選擇的數的個數
{
   if(t >= m) //選夠了,就輸出方案
   {
       for(int i=1;i<=n;i++)
       {
            if(st[i]) cout<<i<<" ";  
       }
       puts("");
       return;
   }
   if(x > n) return;//選擇的數不大於n
   st[x] = true;//選擇x
   dfs(x+1,t+1);//保證每次新加的數大於前一個數,升序
   st[x] = false;//不選擇x
   dfs(x+1,t);
}
int main()
{
    cin>>n>>m;
    dfs(1,0);
    return 0;
}

1209. 帶分數

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 20;

bool st[N];
bool backup[N];
int n,ans;

bool check(int a,int c)
{
    int b = n*c - a*c;//根據題意求出b的大小
    if(a==0 || b==0 || c == 0) return false;//a,b,c不允許任何一個為0
    memcpy(backup,st,sizeof st);//check函式需要單獨的判重陣列
    while(b)//列舉b的每一位
    {
        int a = b % 10;
        b /= 10;
        if(a == 0 || backup[a] == true) return false;//若為0或者已經使用過的數,則失敗
        backup[a] = true;
    }
    for(int i=1;i<=9;i++)
    {
        if(!backup[i]) return false;//若有未使用過的數,則失敗
    }
    return true;
}
void dfs_c(int u,int a,int c)
{
    if(u >= 10) return;//1~9所有數字用完了
    if(check(a,c)) ans++;//檢查當前a和c是否滿足條件
    
    for(int i=1;i<=9;i++)
    {
        if(!st[i])
        {
            st[i] = true;
            dfs_c(u+1,a,c * 10 + i);//注意:第二個引數要求出c的具體大小
            st[i] = false;
        }
    }
}

void dfs_a(int u,int a) //u表示已經使用了多少數字,a表示當前a的大小
{
    if(a >= n) return;//遞迴出口
    if(a) dfs_c(u,a,0);//若a不為0,就列舉一下c
    
    for(int i=1;i<=9;i++)
    {
        if(!st[i])
        {
            st[i] = true;
            dfs_a(u+1,a*10+i);//注意:第二個引數要求出a的具體大小
            st[i] = false;//涉及回溯,必須恢復現場
        }
    }
}
int main()
{
    cin>>n;
    dfs_a(0,0);//a必須從0開始,若從1開始,沒有標記已使用
    cout<<ans<<endl;
    return 0;
}

95. 費解的開關

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N = 6;
int T;
char g[N][N];
char backup[N][N];
int dx[5] = { 0,-1,1,0,0 };
int dy[5] = { 0,0,0,-1,1 };
void turn(int x, int y)
{
    for (int i = 0; i < 5; i++)
    {
        int a = x + dx[i];
        int b = y + dy[i];
        if (a < 0 || a >= 5 || b < 0 || b >= 5) continue;
        if (backup[a][b] == '1') backup[a][b] = '0';
        else backup[a][b] = '1';
    }
}
int main()
{
    cin >> T;
    while (T--)
    {
        int ans = 10;
        for (int i = 0; i < 5; i++)
        {
            cin >> g[i];
        }
        for (int i = 0; i < 32; i++)//以位運算方式列舉第一行操作的所有方案
        {
            int cnt = 0;
            memcpy(backup, g, sizeof g);//先備份,對backup操作
            for (int j = 0; j < 5; j++)//獲取每一位,是1為操作開關,否則不操作開關
            {
                if ((i >> j) & 1 == 1)
                {
                    cnt++;
                    turn(0, j);
                }
            }
            for (int x = 0; x < 4; x++) //每一行開關的操作由前一行狀態唯一確定
            {
                for (int y = 0; y < 5; y++)
                {
                    if (backup[x][y] == '0')
                    {
                        cnt++;
                        turn(x + 1, y);
                    }
                }
            }
            bool f = true;
            for (int y = 0; y < 5; y++) //最後判斷最後一行是否全亮,若沒有,則說明此方案不可取
            {
                if (backup[4][y] == '0')
                {
                    f = false;
                    break;
                }
            }
            if (f) ans = min(ans, cnt); //方案可取,則進行比較,選擇操作次數最小的
        }
        if (ans > 6) ans = -1;//若最小次數都大於6,則說明無解
        cout << ans << endl;
    }
    return 0;
}

116. 飛行員兄弟

1.位運算列舉法

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;
typedef pair<int, int> PII;
vector<PII>tmp,res;
const int N = 5;

char g[N][N],backup[N][N];
void turn(int x,int y)
{
    for(int i=0;i<4;i++)
    {
        if(backup[x][i] == '+') backup[x][i] = '-';
        else backup[x][i] = '+';
        if(backup[i][y] == '+') backup[i][y] = '-';
        else backup[i][y] = '+';
    }
    if(backup[x][y] == '+') backup[x][y] = '-';
    else backup[x][y] = '+';
    return;
}
int main()
{
    for (int i = 0; i < 4; i ++ )
    {
        cin>>g[i];
    }
    for(int op = 0;op< 1<<16;op++)
    {
        vector<PII>tmp;
        memcpy(backup,g,sizeof g);
        for(int i=0;i<16;i++)
        {
            if(op>>i & 1 == 1)
            {
                int x = i/4;
                int y = i%4;
                tmp.push_back({x,y});
                turn(x,y);
            }
        }
        bool f = true;
        for(int i=0;i<4;i++)
        {
            for(int j=0;j<4;j++)
            {
                if(backup[i][j] == '+')
                {
                    f = false;
                    break;
                }
            }
            if(!f) break;
        }
        if(f)
        {
            if(res.empty() || tmp.size() < res.size()) res = tmp;
        }
    }
    int len = res.size();
    cout<<len<<endl;
    for(auto t:res)
    {
        cout<<t.first + 1<<" "<<t.second + 1<<endl;
    }
    return 0;
}

2.dfs列舉法

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
char g[5][5];
typedef pair<int,int>PII;
vector<PII>tmp,res;
void turn(int x,int y)
{
    for(int i=0;i<4;i++)
    {
        if(g[x][i] == '+') g[x][i] = '-';
        else g[x][i] = '+';
    }
    for(int i=0;i<4;i++)
    {
        if(g[i][y] == '+') g[i][y] = '-';
        else g[i][y] = '+';
    }
    if(g[x][y] == '+') g[x][y] = '-'; //x.y處開關操作3次等於操作1次
    else g[x][y] = '+';
    return;
}
void dfs(int x,int y)
{
    if(x == 3 && y == 4)//遞迴出口:列舉完所有開關
    {
        bool f = true;
        for(int i=0;i<4;i++) //檢查所有開關是否為開,否則說明此方案不可行
        {
            for(int j=0;j<4;j++)
            {
                if(g[i][j] == '+')
                {
                    f = false;
                    break;
                }
                if(!f) break;
            }
        }
        if(f)//若是,則進行比較,選擇操作次數較小的
        {
            if(res.empty()||tmp.size() < res.size())
            {
                res = tmp;
            }
        }
        return;
    }
    if(y==4)//換行
    {
        y = 0;
        x++;
    }
    turn(x,y);
    tmp.push_back({x,y});
    dfs(x,y+1);//選擇操作此開關
    
    turn(x,y);//恢復現場
    tmp.pop_back();
    dfs(x,y+1);//選擇不操作此開關
}
int main()
{
    for(int i=0;i<4;i++) cin>>g[i];
    dfs(0,0);
    int len = res.size();
    cout<<len<<endl;
    for(int i=0;i<len;i++)
    {
        cout<<res[i].first + 1<<" "<<res[i].second + 1<<endl;
    }
    return 0;
}