藍橋杯備賽(一) 遞歸與遞推

藍橋杯備賽(一) 遞歸與遞推

1.時間複雜度

1秒能算1億次,即10的8次方。若題目限制爲1秒,則超過10的8次就可能超時。
你所選擇的算法與數據範圍相關。

在這裏插入圖片描述

2.關於cin,cout與scanf,printf的選擇

Cin,cout,與scanf, printf的區別主要在於速度,數據規模較大時,scanf,printf速度明顯快一些。

3.關於遞歸:即一個函數自己調用自己,經典例子爲斐波那契數列

Acwing 92. 遞歸實現指數型枚舉
從 1~n 這 n 個整數中隨機選取任意多個,輸出所有可能的選擇方案。
輸入格式
輸入一個整數n。
輸出格式
每行輸出一種方案。
同一行內的數必須升序排列,相鄰兩個數用恰好1個空格隔開。
對於沒有選任何數的方案,輸出空行。
本題有自定義校驗器(SPJ),各行(不同方案)之間的順序任意。
數據範圍
1≤n≤151≤n≤15
輸入樣例:
3
輸出樣例:

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

#include<iostream>
#include<cstdio>
int n;
int st[16];     //st[]表示狀態,0表示未判斷,1表示選取,2表示不選取
void dfs(int step)
{
    //先寫邊界
    if(step>n)
    {
        for(int i=1;i<=n;i++)  //對於每個數依次判斷是否選取過
            if(st[i]==1)
                printf("%d ",i);
        puts("");   //回車的簡便寫法 
        return ;
    }
    //想象一棵搜索樹,一共n個位置,對於每個位置都有選取與不選兩種選擇
    //第一個分支,選取這個數
    st[step]=1;
    dfs(step+1);
    st[step]=0;  //向上回溯,恢復現場

    //第二個分支,不選取這個數
    st[step]=2;
    dfs(step+1);
    st[step]=0;  //恢復現場

}
int main()
{
    scanf("%d",&n);
    dfs(1);
    getchar();getchar();
    return 0;
}

Acwing 94. 遞歸實現排列型枚舉
把 1~nn 這 nn 個整數排成一行後隨機打亂順序,輸出所有可能的次序。
輸入格式
一個整數n。
輸出格式
按照從小到大的順序輸出所有方案,每行1個。
首先,同一行相鄰兩個數用一個空格隔開。
其次,對於兩個不同的行,對應下標的數一一比較,字典序較小的排在前面。
數據範圍
1≤n≤91≤n≤9
輸入樣例:
3
輸出樣例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

#include<iostream>
#include<cstdio>
using namespace std;
int n;
int a[10];
bool vis[10];
void dfs(int step)
{
    //邊界
    if(step>n)
    {
        for(int i=1;i<=n;i++)
            printf("%d ",a[i]);
        puts("");
    }

    for(int i=1;i<=n;i++)  //對於每個位置開始枚舉每個可以填入的數字
    {
        if(vis[i]==false)
        {
            a[step]=i;
            vis[i]=true;
            dfs(step+1);
            vis[i]=false;  //恢復現場
        }
    }
}
int main()
{
    cin>>n;
    dfs(1);
    getchar();getchar();
    return 0;
}

注意:排列考慮順序,組合不考慮順序

Acwing 93. 遞歸實現組合型枚舉
從 1~n 這 n 個整數中隨機選出 m 個,輸出所有可能的選擇方案。
輸入格式
兩個整數 n,mn,m ,在同一行用空格隔開。
輸出格式
按照從小到大的順序輸出所有方案,每行1個。
首先,同一行內的數升序排列,相鄰兩個數用一個空格隔開。
其次,對於兩個不同的行,對應下標的數一一比較,字典序較小的排在前面(例如1 3 5 7排在1 3 6 8前面)。
數據範圍
n>0n>0 ,
0≤m≤n0≤m≤n ,
n+(n−m)≤25n+(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

//直接用全排列輸出會造成重複
//通過start實現輸出從小到大排列
#include<iostream>
#include<cstdio>
using namespace std;
int n,m;
int a[100];
void dfs(int step,int start)   //start表示當前最小可以枚舉的數,以此來控制a[]爲從小到大排列
{
    if(n-start<m-step) return ;    //剪支,當剩下可選擇的數不夠填的時候直接return
    //邊界
    if(step>m)
    {
        for(int i=1;i<=m;i++)
            printf("%d ",a[i]);
        puts("");
        return ;
    }

    for(int i=start;i<=n;i++)
    {
        a[step]=i;
        dfs(step+1,i+1);  //使得下一個數一定比上一個大
        a[step]=0;  //恢復現場
    }

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


Acwing 1209. 帶分數
100100 可以表示爲帶分數的形式:100=3+69258714100=3+69258714
還可以表示爲:100=82+3546197100=82+3546197
注意特徵:帶分數中,數字 1∼91∼9 分別出現且只出現一次(不包含 00)。
類似這樣的帶分數,100100 有 1111 種表示法。
輸入格式
一個正整數。
輸出格式
輸出輸入數字用數碼 1∼91∼9 不重複不遺漏地組成帶分數表示的全部種數。
數據範圍
1≤N<1061≤N<106
輸入樣例1:
100
輸出樣例1:
11
輸入樣例2:
105
輸出樣例2:
6

方法一:暴力大法:先輸出全排列,然後拆成3個數,再判斷即可

#include<iostream>
#include<cstdio>
using namespace std;
int n,ans;
int str[100];
bool vis[100];
int cal(int l,int r)   //計算函數
{
    int s=0;
    for(int i=l;i<=r;i++)
        s=s*10+str[i];
    return s;
}
void dfs(int step)  
{
   
    if(step>9)
    {
        //將數組分爲3部分,即兩個擋板
        for(int i=1;i<8;i++)    //第一個擋板
            for(int j=i+1;j<9;j++)  //第二個擋板
            {
                int a=cal(1,i);
                int b=cal(i+1,j);
                int c=cal(j+1,9);
                if(c*n==c*a+b)     //注意判斷條件,因爲C++中除法是整除,所以要轉化爲加減乘來計算
                    ans++;
            }
        return ;
    }
    
    for(int i=1;i<=9;i++)  //全排列
    {
        if(vis[i]==false)
        {
            str[step]=i;
            vis[i]=true;
            dfs(step+1);
            vis[i]=false;  //恢復現場
        }
    }

}
int main()
{
    cin>>n;
    dfs(1);
    cout<<ans;
    getchar();getchar();
    return 0;
}


4.遞推

遞推即從前向後一步步推,不存在像遞歸一樣的函數自己調用自己的形式
Acwing 717. 簡單斐波那契
以下數列0 1 1 2 3 5 8 13 21 …被稱爲斐波納契數列。
這個數列從第3項開始,每一項都等於前兩項之和。
輸入一個整數N,請你輸出這個序列的前N項。
輸入格式
一個整數N。
輸出格式
在一行中輸出斐波那契數列的前N項,數字之間用空格隔開。
數據範圍
0<N<460<N<46
輸入樣例:
5
輸出樣例:
0 1 1 2 3

#include<iostream>
#include<cstdio>
using namespace std;
int n;
int a[100];
int main()
{
    a[1]=0;
    a[2]=1;
    cin>>n;
    for(int i=3;i<=n;i++)
        a[i]=a[i-1]+a[i-2];
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    getchar();getchar();
    return 0;
}

Acwing 95. 費解的開關
你玩過“拉燈”遊戲嗎?25盞燈排成一個5x5的方形。每一個燈都有一個開關,遊戲者可以改變它的狀態。每一步,遊戲者可以改變某一個燈的狀態。遊戲者改變一個燈的狀態會產生連鎖反應:和這個燈上下左右相鄰的燈也要相應地改變其狀態。
我們用數字“1”表示一盞開着的燈,用數字“0”表示關着的燈。下面這種狀態
10111
01101
10111
10000
11011
在改變了最左上角的燈的狀態後將變成:
01111
11101
10111
10000
11011
再改變它正中間的燈後狀態將變成:
01111
11001
11001
10100
11011
給定一些遊戲的初始狀態,編寫程序判斷遊戲者是否可能在6步以內使所有的燈都變亮。
輸入格式
第一行輸入正整數nn,代表數據中共有nn個待解決的遊戲初始狀態。
以下若干行數據分爲nn組,每組數據有5行,每行5個字符。每組數據描述了一個遊戲的初始狀態。各組數據間用一個空行分隔。
輸出格式
一共輸出nn行數據,每行有一個小於等於6的整數,它表示對於輸入數據中對應的遊戲狀態最少需要幾步才能使所有燈變亮。
對於某一個遊戲初始狀態,若6步以內無法使所有燈變亮,則輸出“-1”。
數據範圍
0<n≤5000<n≤500
輸入樣例:
3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111
輸出樣例:
3
2
-1

//發現自己太菜了,位運算幾乎一點都不會,嗚嗚嗚~~~
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n;
char a[6][6],backup[6][6];  //backup是a的備份
int dx[5]={0,-1,1,0,0},dy[5]={0,0,0,1,-1};
void turn(int x,int y)
{
    for(int i=0;i<5;i++)
    {
        int xi=x+dx[i],yi=y+dy[i];
        if(xi>=0&&xi<5&&yi>=0&&yi<5)  //判斷是否出界
            a[xi][yi]=a[xi][yi]^1;     //參與運算的兩個值,如果兩個相應位相同,則結果爲0,否則爲1
    }
}
int main()
{
    cin>>n;
    while(n--)
    {
        for(int i=0;i<5;i++)  cin>>a[i];  //輸入
        //枚舉第一行狀態
        int s=10;   //記錄步數
        for(int op=0;op<1<<5;op++)    //5位二進制數表示第一行狀態,相當於十進制0到32(1<<5表示32)
        {
            int step=0;
            memcpy(backup,a,sizeof(a));  //備份一份,判斷下一個矩陣的時候還原
            //按照上面枚舉的對第一行操作
            for(int i=0;i<5;i++)
            {
                if(op>>i&1) //op的第i位是否爲1
                {
                    step++;
                    turn(0,i);
                }
            }
            //對剩下的4行操作
            for(int i=0;i<4;i++)
                for(int j=0;j<5;j++)
                {
                    if(a[i][j]=='0')
                    {
                        step++;
                        turn(i+1,j);  //通過按下一行來保證上一行的燈全滅
                    }
                }
            //判斷最後一行燈是否全滅
            int k;
            for(k=0;k<5;k++)
            {
                if(a[4][k]=='0')
                    break;
            }
            if(k==5)
                s=min(s,step);
            memcpy(a,backup,sizeof(backup));  //記得還原原來的狀態
        }
        //輸出
        if(s>6)
            cout<<-1;
        else
            cout<<s;
        puts("");
        
    }
    getchar();getchar();
    return 0;
}

Acwing 1208. 翻硬幣
小明正在玩一個“翻硬幣”的遊戲。
桌上放着排成一排的若干硬幣。我們用 * 表示正面,用 o 表示反面(是小寫字母,不是零)。
比如,可能情形是:**oo***oooo
如果同時翻轉左邊的兩個硬幣,則變爲:oooo***oooo
現在小明的問題是:如果已知了初始狀態和要達到的目標狀態,每次只能同時翻轉相鄰的兩個硬幣,那麼對特定的局面,最少要翻動多少次呢?
我們約定:把翻動相鄰的兩個硬幣叫做一步操作。
輸入格式
兩行等長的字符串,分別表示初始狀態和要達到的目標狀態。
輸出格式
一個整數,表示最小操作步數
數據範圍
輸入字符串的長度均不超過100。
數據保證答案一定有解。
輸入樣例1:

**********
o****o****

輸出樣例1:
5
輸入樣例2:

*o**o***o***
*o***o**o***

輸出樣例2:
1

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
string a,b;
void turn(int i)
{
    if(a[i]=='*')
        a[i]='o';
    else
        a[i]='*';
}
int main()
{
    int step=0;
    cin>>a>>b;
    for(int i=0;i<a.length();i++)
    {
        if(a[i]!=b[i])    //每次保證前面的狀態是正確狀態,一個一個往下推即可
        {
            turn(i);
            turn(i+1);
            step++;
        }
    }
    cout<<step;
    getchar();getchar();
    return 0;
}


有關vector的介紹
https://www.runoob.com/w3cnote/cpp-vector-container-analysis.html
https://blog.csdn.net/weixin_41743247/article/details/90635931
acwing 116. 飛行員兄弟
“飛行員兄弟”這個遊戲,需要玩家順利的打開一個擁有16個把手的冰箱。
已知每個把手可以處於以下兩種狀態之一:打開或關閉。
只有當所有把手都打開時,冰箱纔會打開。
把手可以表示爲一個4х4的矩陣,您可以改變任何一個位置[i,j]上把手的狀態。
但是,這也會使得第i行和第j列上的所有把手的狀態也隨着改變。
請你求出打開冰箱所需的切換把手的次數最小值是多少。
輸入格式
輸入一共包含四行,每行包含四個把手的初始狀態。
符號“+”表示把手處於閉合狀態,而符號“-”表示把手處於打開狀態。
至少一個手柄的初始狀態是關閉的。
輸出格式
第一行輸出一個整數N,表示所需的最小切換把手次數。
接下來N行描述切換順序,每行輸入兩個整數,代表被切換狀態的把手的行號和列號,數字之間用空格隔開。
注意:如果存在多種打開冰箱的方式,則按照優先級整體從上到下,同行從左到右打開。
數據範圍
1≤i,j≤41≤i,j≤4
輸入樣例:

-+--
----
----
-+--

輸出樣例:
6
1 1
1 3
1 4
4 1
4 3
4 4

菜雞做法:(不怎麼會用vector,嗚嗚嗚,奇怪的是比用vector的做法還要快一些,嘻嘻)

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

#define x first      //x代替first
#define y second     //y代替second

using namespace std;

char a[5][5],backup[5][5];

int get(int i,int j)
{
    return 4*i+j;
}
void turn(int x,int y)
{
    if(a[x][y]=='+')
        a[x][y]='-';
    else
        a[x][y]='+';
    
}
void turn_all(int x,int y)
{
    for(int i=0;i<4;i++)
    {
        turn(x,i);
        turn(i,y);
    }
    turn(x,y);
}
int main()
{
    for(int i=0;i<4;i++)  cin>>a[i];
    int res[100][3];int step=100;
    memset(res,0,sizeof(res));
    memcpy(backup,a,sizeof(a));

    for(int op=0;op<1<<16;op++)  //枚舉所有可能
    {
        int temp[100][3];
        int k=0;
        for(int i=0;i<4;i++)
            for(int j=0;j<4;j++)
                if(op>>get(i,j)&1)     //判斷是否爲1
                {
                    temp[k][0]=i;
                    temp[k][1]=j;
                    turn_all(i,j);
                    k++;
                }

        //判斷所有燈是否全亮
        bool p=false;
        for(int i=0;i<4;i++)
            for(int j=0;j<4;j++)
                if(a[i][j]=='+')
                    p=true;
        //符合要求就存入
        if(p==false)
        {
            if(k<step)
            {
                step=min(step,k);      
                memcpy(res,temp,sizeof(temp));  //若當前步數更少就取它
            }
        }
        memcpy(a,backup,sizeof(backup));    //每次循環前記得還原
    }
    //輸出
    cout<<step<<endl;
    for(int i=0;i<step;i++)
        cout<<res[i][0]+1<<" "<<res[i][1]+1<<endl;

    getchar();getchar();
    return 0;
}


使用vector做法:

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

#define x first
#define y second

using namespace std;
typedef pair<int,int> PII;
char a[5][5],backup[5][5];
int get(int x,int y)  //將矩陣對應到一行上
{
    return x*4+y;
}

void turn_one(int x, int y)
{
    if (a[x][y] == '+') a[x][y] = '-';
    else a[x][y] = '+';
}

void turn_all(int x, int y)
{
    for (int i = 0; i < 4; i ++ )  //橫行豎列都翻轉
    {
        turn_one(x, i);
        turn_one(i, y);
    }
    turn_one(x, y);  //由於中間的翻了兩次,需要抵消一次
}

int main()
{
    for(int i=0;i<4;i++)  cin>>a[i];

    vector<PII> res;
    for(int op=0;op<1<<16;op++)   //對每一個開關進行二進制枚舉
    {
        vector<PII> temp;  //存儲當前方案
        memcpy(backup,a,sizeof(a));  //備份

        for(int i=0;i<4;i++)
            for(int j=0;j<4;j++)
                if(op>>get(i,j)&1)  //如果是1就操作按下開關
                {
                    temp.push_back({i,j});    //將方案存到temp中
                    turn_all(i,j);       //翻轉
                }
        //判斷所有燈泡是否全亮
        bool has_closed=false;
        for(int i=0;i<4;i++)
            for(int j=0;j<4;j++)
                if(a[i][j]=='+')
                    has_closed=true;
        
        if(has_closed==false)
        {
            if(res.empty()||res.size()>temp.size())
                res=temp;
        }
        memcpy(a,backup,sizeof(backup));  //還原
    }
    cout<<res.size()<<endl;
    for(int i=0;i<res.size();i++)
        cout<<res[i].x+1<<' '<<res[i].y+1<<endl;
    getchar();getchar();
    return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章