算法學習 遞歸和遞推

例題1 遞歸實現指數型枚舉

遞推和遞歸問題,畫樹解決

題目

從 1~n 這 n 個整數中隨機選取任意多個,輸出所有可能的選擇方案。

輸入格式

輸入一個整數n。

輸出格式

每行輸出一種方案。

同一行內的數必須升序排列,相鄰兩個數用恰好1個空格隔開。

對於沒有選任何數的方案,輸出空行。

本題有自定義校驗器(SPJ),各行(不同方案)之間的順序任意。

數據範圍

1≤n≤15

輸入樣例:

3

輸出樣例:

3
2
2 3
1
1 3
1 2
1 2 3

代碼

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

using namespace std;

const int N = 16;  //數據的範圍
int n;
int st[N];   //一個狀態的數組,0 沒決定    1   選擇  2  不選擇

void dfs(int u)  //深搜
{
    if (u > n)   //如果判定到最後一個數,則輸出情況   判定的條件要寫在最前面
    {
        for(int i = 0;i < n;i++)   //循環遍歷這個數組
        {
            if(st[i + 1] == 1)
            {
                printf("%d ",i + 1);
            }
        }
        printf("\n");
        return;
    }
    st[u] = 2;   //不選的情況    遞歸的好處  兩者不相互影響
    dfs(u + 1);
    st[u] = 0;   //恢復現場
    
    st[u] = 1;   //選擇的情況
    dfs(u + 1);
    st[u] = 0;
}


int main()
{
    cin >> n;
    dfs(1);
}

例題2 遞歸實現排列型枚舉

遞歸和遞推的時候切記恢復原狀

題目

把 1~n 這 n 個整數排成一行後隨機打亂順序,輸出所有可能的次序。

輸入格式

一個整數n。

輸出格式

按照從小到大的順序輸出所有方案,每行1個。

首先,同一行相鄰兩個數用一個空格隔開。

其次,對於兩個不同的行,對應下標的數一一比較,字典序較小的排在前面。

數據範圍

1≤n≤9

輸入樣例:

3

輸出樣例:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

代碼

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

using namespace std;

const int N = 10;  //表示數據的範圍
int n;
int state[N];   //表示狀態   0代表空  1~n代表有
bool used[N];     //ture 表示用過  false表示沒用過


void dfs(int u)   //u表示在第幾個位置
{
    //判定界限、輸出可能性
    if(u > n)
    {
        for(int i = 1;i <= n;i++) printf("%d ",state[i]);
        puts("");  //表示換行
        return;   //結束
    }

    //依次枚舉各個分支,即當前位置可以填哪些數
    for(int i = 1;i <= n;i++)
    { 
        if(!used[i])   //判定這個位置是空的情況
        {
            state[u] = i;
            used[i] = true;    //表示這個數已經用過了
            dfs(u + 1);

            //恢復原狀
            state[u] = 0;
            used[i] = false;
        }
    }

}

int main(){
    scanf("%d",&n);
    dfs(1);    //表示從第一個位置開始
}

例題3 簡單斐波那契

題目

以下數列0 1 1 2 3 5 8 13 21 …被稱爲斐波納契數列。

這個數列從第3項開始,每一項都等於前兩項之和。

輸入一個整數N,請你輸出這個序列的前N項。

輸入格式

一個整數N。

輸出格式

在一行中輸出斐波那契數列的前N項,數字之間用空格隔開。

數據範圍

0<N<46

輸入樣例:

5

輸出樣例:

0 1 1 2 3

代碼

#include<bits/stdc++.h>
using namespace std;
int n,feibo[99999];
int main(){
    scanf("%d",&n);
    feibo[1]=0,feibo[2]=1;
    for(int i=3;i<=n;++i) feibo[i]=feibo[i-1]+feibo[i-2];
    for(int i=1;i<=n;++i) printf("%d ",feibo[i]);
    return 0;
}

例題4 費解的開關

遞推和位運算

題目

你玩過“拉燈”遊戲嗎?25盞燈排成一個5x5的方形。每一個燈都有一個開關,遊戲者可以改變它的狀態。每一步,遊戲者可以改變某一個燈的狀態。遊戲者改變一個燈的狀態會產生連鎖反應:和這個燈上下左右相鄰的燈也要相應地改變其狀態。

我們用數字“1”表示一盞開着的燈,用數字“0”表示關着的燈。下面這種狀態

10111
01101
10111
10000
11011
在改變了最左上角的燈的狀態後將變成:

01111
11101
10111
10000
11011
再改變它正中間的燈後狀態將變成:

01111
11001
11001
10100
11011
給定一些遊戲的初始狀態,編寫程序判斷遊戲者是否可能在6步以內使所有的燈都變亮。

輸入格式

第一行輸入正整數n,代表數據中共有n個待解決的遊戲初始狀態。

以下若干行數據分爲n組,每組數據有5行,每行5個字符。每組數據描述了一個遊戲的初始狀態。各組數據間用一個空行分隔。

輸出格式

一共輸出n行數據,每行有一個小於等於6的整數,它表示對於輸入數據中對應的遊戲狀態最少需要幾步才能使所有燈變亮。

對於某一個遊戲初始狀態,若6步以內無法使所有燈變亮,則輸出“-1”。

數據範圍

0<n≤500

輸入樣例:

3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111

輸出樣例:

3
2
-1

代碼

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

using namespace std;

const int N = 6;
char g[N][N],backup[N][N];
int dx[5] = {-1,0,1,0,0},dy[5] = {0,1,0,-1,0};

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;    //在邊界外,直接忽略即可
    g[a][b] ^= 1;   //這個操作非常牛逼,位運算   主要是用來把二進制最後一位改變    0 變成  1    1  變成  0
    }
}



int main()
{
    int T;  //T是有幾組數據
    cin >> T;
    while(T --)
    {
        for(int i = 0;i < 5;i++) cin >> g[i];
        
        int res = 10;  //與步數相比較的值
        //枚舉第一行的操作
        for (int op = 0;op < 32;op ++)
        {
            memcpy(backup,g,sizeof g);   //先把g備份給  backup
            int step = 0;   //步數
            for (int i = 0;i < 5;i++)     //對第一行進行操作
            {
                if (op >> i & 1) 
                {
                    step++;
                    turn(0,i);
                }
            }
            
            //對第一行到第四行進行操作
            for(int i = 0;i < 4;i++)
            {
                for(int j = 0;j < 5;j++)
                {
                    if(g[i][j] == '0') 
                    {
                        step++;
                        turn(i + 1,j);
                    }
                }
            }
            
            //判斷
            bool dark = false;
            //判斷最後的一行
            for(int i = 0;i < 5;i ++)
            {
                if(g[4][i] == '0')
                {
                    dark = true;
                    break;
                }
            }
            //判斷出最後一行有沒有黑的燈
            if(!dark) res = min(res,step);
            memcpy(g,backup,sizeof g);
        }
        if (res > 6) res = -1;
        cout << res <<  endl;
    }
    return 0;
}

例題5 遞歸實現組合性枚舉

題目

從 1~n 這 n 個整數中隨機選出 m 個,輸出所有可能的選擇方案。

輸入格式

兩個整數 n,m ,在同一行用空格隔開。

輸出格式

按照從小到大的順序輸出所有方案,每行1個。

首先,同一行內的數升序排列,相鄰兩個數用一個空格隔開。

其次,對於兩個不同的行,對應下標的數一一比較,字典序較小的排在前面(例如1 3 5 7排在1 3 6 8前面)。

數據範圍

n>0 ,
0≤m≤n ,
n+(n−m)≤25

輸入樣例:

5 3

輸出樣例:

1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5

遞歸實現代碼

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

using namespace std;

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

void dfs(int u,int start)
{
    if (u + n - start < m) return;
    //邊界的情況
    if (u == m + 1)  //夠了
    {
        for(int i = 1;i <= m;i++)
        {
            printf("%d ",way[i]);
        }
        puts("");
        return;
    }
    for(int i = start;i <= n;i++)    //執行遞歸操作
    {
        way[u] = i;   //先賦值
        dfs(u + 1,i + 1);
        way[u] = 0;    //恢復現場
    }
    
}

int main()
{
    scanf("%d%d",&n,&m);
    dfs(1,1);    //第一個數表示到哪裏了,第二個數表示從哪裏開始
    return 0;
}

非遞歸實現代碼

這題直接二進制表示所有情況,
也就是for(i=1;i<1<<n;i++)
如果1的個數是m,就輸出1的位置
簡單的二進制操作找1的個數和位置

#include<queue>
#include<bitset>	
#include<string>
#include<iostream>
#include<cstdio>
#include<map>
#include<algorithm>
using namespace std;

const int N=1<<21,inf=0x3f3f3f3f;
int n,m;
map<int ,int > m1;

struct point{
    vector<int > v1;
}p1[N];

int lowbit(int x)
{
    return x&(-x);
}

bool cmp(point x,point y){
    int ptail=0;
    while(x.v1[ptail]==y.v1[ptail])ptail++;
    return x.v1[ptail]<y.v1[ptail];
}

int main(){
    cin>>n>>m;
    bitset<26> b1;
    m1[1]=0;
    int q=2,tail=0;

    for(int i=1;i<=32;i++){
        m1[q]=i;
        q*=2;
    }

    for(int a=0;a<(1<<n);a++){
        b1|=a;
        if(b1.count()==m){
            int pa=a;
            while(pa){
            p1[tail].v1.push_back(m1[lowbit(pa)]+1);
            pa^=lowbit(pa);
            }
            tail++;
        }
        b1&=0;
    }
    sort(p1,p1+tail,cmp);
    for(int a=0;a<tail;a++){
        for(int b=0;b<m;b++)printf("%d ",p1[a].v1[b]);
        printf("\n");
    }
}

例題6 帶分數

題目

100 可以表示爲帶分數的形式:100=3+69258714
還可以表示爲:100=82+3546197
注意特徵:帶分數中,數字 1∼9 分別出現且只出現一次(不包含 0)。

類似這樣的帶分數,100 有 11 種表示法。

輸入格式

一個正整數。

輸出格式

輸出輸入數字用數碼 1∼9 不重複不遺漏地組成帶分數表示的全部種數。

數據範圍

1≤N<106

輸入樣例1:

100

輸出樣例1:

11

輸入樣例2:

105

輸出樣例2:

6

代碼

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

using namespace std;

const int N = 20;

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

bool check(int a,int c)
{
    int b = n * c - a * c;
    if (!a || !b || !c) return false;   //三者當中任意一者不存在
    
    memcpy(backup,st,sizeof st);  //把st數組給了backup數組
    while(b)   //判定b  和  a  c  有沒有重複出現的數字
    {
        int x = b % 10;    //取個位
        b /= 10;       //個位刪掉
        if(!x || backup[x])  return false;   //x已經被佔用的情況
        backup[x] = true;                    //被佔用
    }
    
    for (int i = 1;i <= 9;i++)
    {
        if (!backup[i])   return false;    //backup  中還有沒填滿的數
    }
    return true;
    
}

void dfs_c(int u,int a,int c)
{
    if(u == n)  return;   //滿了的情況
    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);
            st[i] = false;   //恢復原狀
        }
    }
}



void dfs_a(int u,int a)       //u  遍歷到第幾位了  a  就是a 的值
{
    if (a >= n) return;     //a>=n  滿了
    if (a) dfs_c(u,a,0);    //如果a 存在的話,就遞歸c
    
    //輸出
    for (int i = 1;i <= 9;i++)
    {
        if(!st[i])     //如果這個沒被用的話  
        {
            st[i] = true;
            dfs_a(u + 1,a * 10 + i);
            st[i] = false;  //恢復原狀
        }
    }
    
}

int main()
{
    cin >> n;
    dfs_a(0,0);
    cout << ans <<endl;
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章