递归与递推

 目录

递归实现指数型枚举

递归实现排列型枚举 

递归实现组合型枚举

 简单斐波拉契

费解的开关 

带分数 

题目描述

输入格式

输出格式

输入输出样例

说明/提示

 翻硬币

题目背景

题目描述

输入格式

输出格式

输入输出样例

说明/提示

飞行员兄弟 

输入格式

输出格式

数据范围

输入样例:

输出样例:


递归实现指数型枚举

递归实现指数型枚举 (nowcoder.com)

92. 递归实现指数型枚举 - AcWing题库

题目描述
从 1~n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。

输入格式
输入一个整数n。

输出格式
每行输出一种方案。

同一行内的数必须升序排列,相邻两个数用恰好1个空格隔开。

对于没有选任何数的方案,输出空行。

本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。

数据范围
1≤n≤15

输入样例:

3

输出样例:


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

 思路:遍历0~n,表示0~n时的各种情况个数,然后递归查找满足的情况

#include<bits/stdc++.h>
using namespace std;
int b[20];
void dg(int n,int p,int w){
    if(w==p){//表示选出来了,输出结果
        for(int i=0;i<w;i++)cout<<b[i]<<' ';
        cout<<endl;
        return;
    }
    for(int i=1;i<=n;i++){
        if(w==0||(i>b[w-1])){
            b[w]=i;
            dg(n,p,w+1);
        }
    }
}
int main(){
    int n;
    cin>>n;
    for(int i=0;i<=n;i++)dg(n,i,0);//其中n指总的个数;i表示一共选多少个;0表示一开始是0个,充当计数器
}

y总思路:采用填坑模式,对于所有的数字,只有选和不选两种情况,枚举出所有的情况即为答案

#include<bits/stdc++.h>
using namespace std;
int b[20];// 状态,记录每个位置当前的状态:0表示还没考虑,1表示选它,2表示不选它
void dg(int n,int w){//w表示当前位置,选还是不选
    if(w>n){
        for(int i=1;i<=n;i++)
            if(b[i]==1)cout<<i<<' ';
        cout<<endl;
        return;
    }
    b[w]=2;//不选
    dg(n,w+1);
    b[w]=0;//恢复现场
    b[w]=1;//选
    dg(n,w+1);
    b[w]=0;
}
int main(){
    int n;
    cin>>n;
    dg(n,1);
}

递归实现排列型枚举 

94. 递归实现排列型枚举 - AcWing题库

递归实现排列型枚举 (nowcoder.com)

题目描述
把 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

思路1:STL选手,直接上板子

#include <bits/stdc++.h>
using namespace std;
int n, a[10];
int main() {
    cin >> n;
    for (int i = 0; i < n; i++)
        a[i] = i + 1;
    do {
        for (int i = 0; i < n; i++)
            cout << a[i] << ' ';
        cout << endl;
    } while (next_permutation(a, a + n));
    return 0;
}

思路2:dfs解法

#include <bits/stdc++.h>
using namespace std;
int n, a[10],b[10];
void dfs(int x){
    if(x==n){
        for(int i=0;i<n;i++){
            cout<<a[i]<<' ';
        }
        cout<<endl;
    }
    for(int i=1;i<=n;i++){
        if(!b[i]){
            b[i]++;
            a[x]=i;
            dfs(x+1);
            a[x]=0;
            b[i]--;
        }
    }
}
int main() {
    cin>>n;
    dfs(0);
    return 0;
}

递归实现组合型枚举

93. 递归实现组合型枚举 - AcWing题库

递归实现组合型枚举 (nowcoder.com)

题目描述
从 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 

dfs版本

#include <bits/stdc++.h>
using namespace std;
int n,m, a[25],b[25];
void dfs(int x,int w){//x表示个数,w为了保存升序剪枝
    if(x==m){
        for(int i=0;i<m;i++){
            cout<<a[i]<<' ';
        }
        cout<<endl;
    }
    for(int i=w+1;i<=n;i++){
        if(!b[i]){
            b[i]++;
            a[x]=i;
            dfs(x+1,i);
            a[x]=0;
            b[i]--;
        }
    }
}
int main() {
    cin>>n>>m;
    dfs(0,0);
    return 0;
}

思路:参照填坑做法,对每个位置只有选和不选两种情况,枚举出所有情况

#include<bits/stdc++.h>
using namespace std;
int n,m;
int b[55];// 状态,记录每个位置当前的状态:0表示还没考虑,1表示选它,2表示不选它
void dg(int w,int cnt){//w表示当前位置,选还是不选,cnt表示计数器
    if(cnt>=m){
        for(int i=1;i<=n;i++)
            if(b[i]==1)cout<<i<<' ';
        cout<<endl;
        return;
    }
    if(w>n)return;
    b[w]=1;//选
    dg(w+1,cnt+1);
    b[w]=2;//不选
    dg(w+1,cnt);
}
int main(){
    cin>>n>>m;
    dg(1,0);
}

 简单斐波拉契

717. 简单斐波那契 - AcWing题库

题目描述
以下数列 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,a[50];
void dg(int x){
    if(x>n)return;//超出范围
    if(x==1){//等于1
        a[x]=0;
        cout<<a[x]<<' ';
        dg(x+1);
    }
    else if(x==2){//等于2
        a[x]=1;
        cout<<a[x]<<' ';
        dg(x+1);
    }
    else{//其他
        a[x]=a[x-1]+a[x-2];
        cout<<a[x]<<' ';
        dg(x+1);
    }
}
int main(){
    cin>>n;
    dg(1);
    return 0;
}

思路2:直接暴力

#include<bits/stdc++.h>
using namespace std;
int n,a[50];
int main(){
    cin>>n;
    int x=0,y=1;//初始的两个值
    for(int i=0;i<n;i++){
        cout<<x<<' ';
        y=x+y;//将x+y的值存入y中
        x=y-x;//x+y-x,即将更新前的y的值存入x中
    }
    return 0;
}

费解的开关 

95. 费解的开关 - AcWing题库

你玩过“拉灯”游戏吗?

25 盏灯排成一个 5×5 的方形。

每一个灯都有一个开关,游戏者可以改变它的状态。

每一步,游戏者可以改变某一个灯的状态。

游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。

我们用数字 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

思路: 首先倒推求出所有的可能字符串对应的最小值,如何每次判断即可

关于unordered_map的用法参考下方博客

C++中的unordered_map用法详解_zou_albert的博客-CSDN博客

#include<bits/stdc++.h>
using namespace std;
int ans;
int dx[] = { 0,1,0,-1,0 };
int dy[] = { 0,0,1,0,-1 };
string a,b;
unordered_map<string, int>m;
string turn(string a,int x) {//将字符串a上以x为中心进行反转
    for (int i = 0; i < 5; i++) {
        int ax = x / 5 + dx[i];
        int ay = x % 5 + dy[i];
        if (ax >= 0 && ax < 5 && ay >= 0 && ay < 5)a[ax * 5 + ay] = (1 - (a[ax * 5 + ay] - '0') + '0');
    }
    return a;
}
void bfs(){
    queue<string>q;
    q.push(a);
    m[a]=1;
    while(q.size()){
        string s=q.front();
        q.pop();
        if(m[s]==7)break;
        for(int i=0;i<25;i++){
            string p=turn(s,i);
            if(!m.count(p)){//如果p没有出现过
                m[p]=m[s]+1;
                q.push(p);
            }
        }
    }
}
int main() {
    for (int i = 0; i < 25; i++) {//将a设为最终状态
        a+='1';
    }
    bfs();//通过bfs求出6即6以内的各种状态的字符串
    int t;
    cin>>t;
    while(t--){
        a="";
        for(int i=0;i<5;i++){
            string x;
            cin>>x;
            a+=x;
        }
        printf("%d\n",m[a]-1);
    }
    return 0;
}

状态压缩版本,比前面一个快了将近一倍

#include <bits/stdc++.h>
using namespace std;
unordered_map<int, int>m;
int turn(int x, int p){
    x ^= (1 << p);
    if (p % 5) x ^= (1 << (p - 1));
    if (p >= 5) x ^= (1 << (p - 5));
    if (p < 20) x ^= (1 << (p + 5));
    if ((p % 5) < 4) x ^= (1 << (p + 1));
    return x;
}
void bfs(){
    queue<int> q;
    int x = (1 << 25) - 1;//状态压缩,共2^25种状态,0-2^25-1(25个1)
    m[x] = 1;
    q.push(x);
    while (q.size()){
        int p = q.front();
        q.pop();
        if (m[p] == 7) break;
        for (int i = 0; i < 25; i++){
            x = turn(p, i);
            if (!m.count(x)){
                m[x] = m[p] + 1;
                q.push(x);
            }
        }
    }
}
int main(){
    bfs();
    int t;
    cin>>t;
    while (t--)
    {
        int sum = 0;
        for (int i = 0; i < 25; i++)
        {
            char x;
            cin >> x;
            sum += ((x - '0') << i);
        }
        cout<<m[sum] - 1<<"\n";
    }
    return 0;
}

递推版本

#include<bits/stdc++.h>
using namespace std;
string s;
int b[5];
int ans, cnt;
int dx[] = { 0,1,0,-1,0 };
int dy[] = { 0,0,1,0,-1 };
string turn(int x, string a) {//翻转字符串
    for (int i = 0; i < 5; i++) {
        int ax = x / 5 + dx[i];
        int ay = x % 5 + dy[i];
        if (ax >= 0 && ax < 5 && ay >= 0 && ay < 5) {
            a[ax * 5 + ay] = (1 - (a[ax * 5 + ay] - '0')) + '0';
        }
    }
    return a;
}
void pd(string a) {
    for (int i = 1; i < 5; i++) {//递推判断1~4行的
        for (int j = 0; j < 5; j++) {
            if (a[(i - 1) * 5 + j] == '0') {
                a = turn(i * 5 + j, a);
                cnt++;
            }
        }
    }
    for (int i = 0; i < 5; i++) {//判断是否可行
        if (a[4 * 5 + i] == '0') {
            return;
        }
        if (i == 4) {
            ans = min(ans, cnt);
            return;
        }
    }
}
void dfs(int x, int p, string a) {
    if (x == p) {//如果正好选了p个
        pd(a);//判断是否可行
        cnt = p;//cnt置为p
        return;
    }
    for (int i = 0; i < 5; i++) {
        if (!b[i]) {
            b[i]++;
            a = turn(i, a);
            dfs(x + 1, p, a);
            a = turn(i, a);
            b[i]--;
        }
    }
}
int main() {
    int t;
    cin >> t;
    while (t--) {
        ans = 7;
        s.clear();
        for (int i = 0; i < 5; i++) {//输入
            string x;
            cin >> x;
            s += x;
        }
        for (int i = 0; i <= 5; i++) {//选i个
            cnt = i;
            dfs(0, i, s);
        }
        //输出结果
        if (ans == 7)cout << -1;
        else cout << ans;
        cout << "\n";
    }
    return 0;
}

带分数 

题目描述

100100 可以表示为带分数的形式:100=3+69258714100=3+71469258​。

还可以表示为:100=82+3546197100=82+1973546​。

注意特征:带分数中,数字 11 ~ 99 分别出现且只出现一次(不包含 00)。

类似这样的带分数,100100 有 1111 种表示法。

输入格式

从标准输入读入一个正整数 �(�<106)N(N<106)。

输出格式

程序输出数字 �N 用数码 11 ~ 99 不重复不遗漏地组成带分数表示的全部种数。

注意:不要求输出每个表示,只统计有多少表示法!

输入输出样例

输入 #1复制

100

输出 #1复制

11

输入 #2复制

105

输出 #2复制

6

说明/提示

原题时限 3 秒, 64M。蓝桥杯 2013 年第四届省赛

带分数要满足x+y/z==n,那么y/z必须整除

全排列,如果满足条件就计数器+1

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int n,cnt;
int a[10];
int main() {
    cin >> n;
    for (int i = 1; i < 10; i++)a[i - 1] = i;
    do {
        for (int i = 0; i < 7; i++) {//切割
            int x = 0;
            for (int p = 0; p <= i; p++)x = x * 10 + a[p];
            for (int j = i + 1; j < 8; j++) {//切割
                int y = 0, z = 0;
                for (int p = i + 1; p <= j; p++)y = y * 10 + a[p];
                for (int p = j + 1; p < 9; p++)z = z * 10 + a[p];
                if (z != 0 && x + y / z == n && y % z == 0) {
                    cnt++;
                }
            }
        }
    } while (next_permutation(a, a + 9));//全排列函数
    cout << cnt;
    return 0;
}

字符串版本,substr表示切割函数,stoi表示字符串转int函数

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int n,cnt;
int x, y, z;
int main() {
    cin >> n;
    string s = "123456789";
    do {
        for (int i = 1; i <= 7; i++) {
            x = stoi(s.substr(0,i));//切割
            if (x > n)break;//剪枝
            for (int j = i+1; j <= 8; j++) {//切割
                y = stoi(s.substr(i, j - i));
                z = stoi(s.substr(j));
                if (x + y / z == n && y % z == 0) {
                    cnt++;
                }
            }
        }
    } while (next_permutation(s.begin(),s.end()));
    cout << cnt;
    return 0;
}

 翻硬币

题目背景

小明正在玩一个“翻硬币”的游戏。

题目描述

桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零),比如可能情形是 **oo***oooo,如果同时翻转左边的两个硬币,则变为 oooo***oooo。现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?

输入格式

两行等长字符串,分别表示初始状态和要达到的目标状态,每行长度小于 10001000。

数据保证一定存在至少一种方案可以从初始状态和要达到的目标状态。

输出格式

一个整数,表示最小操作步数。

输入输出样例

输入 #1复制

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

输出 #1复制

5

输入 #2复制

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

输出 #2复制

1

说明/提示

source:蓝桥杯 2013 省 B 组 H 题

很简单的一个递推问题

#include <iostream>
using namespace std;
int main()
{
  string s,a;
  int cnt=0;
  cin>>s>>a;
  for(int i=0;i<s.size();i++){
    if(s[i]!=a[i]){
      cnt++;
        if(s[i+1]=='o'){
          s[i+1]='*';
        }
        else{
          s[i+1]='o';
        }
    }
  }
  cout<<cnt;
  return 0;
}

飞行员兄弟 

“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有 1616 个把手的冰箱。

已知每个把手可以处于以下两种状态之一:打开或关闭。

只有当所有把手都打开时,冰箱才会打开。

把手可以表示为一个 4×44×4 的矩阵,您可以改变任何一个位置 [i,j][�,�] 上把手的状态。

但是,这也会使得第 i� 行和第 j� 列上的所有把手的状态也随着改变。

请你求出打开冰箱所需的切换把手的次数最小值是多少。

输入格式

输入一共包含四行,每行包含四个把手的初始状态。

符号 + 表示把手处于闭合状态,而符号 - 表示把手处于打开状态。

至少一个手柄的初始状态是关闭的。

输出格式

第一行输出一个整数 N�,表示所需的最小切换把手次数。

接下来 N� 行描述切换顺序,每行输出两个整数,代表被切换状态的把手的行号和列号,数字之间用空格隔开。

注意:如果存在多种打开冰箱的方式,则按照优先级整体从上到下,同行从左到右打开。

数据范围

1≤i,j≤41≤�,�≤4

输入样例:
-+--
----
----
-+--
输出样例:
6
1 1
1 3
1 4
4 1
4 3
4 4

数据量较小,可以直接dfs求出答案,对于每一个位置,只有选和不选两者情况

#include<bits/stdc++.h>
using namespace std;
string a[4];
vector<pair<int,int>>v,ans;
void turn(int x){//翻转函数
    int l=x/4;//hang
    int r=x%4;//lie
    for(int i=0;i<4;i++){
        if(a[l][i]=='+')a[l][i]='-';
        else a[l][i]='+';
        if(a[i][r]=='+')a[i][r]='-';
        else a[i][r]='+';
    }
    if(a[l][r]=='+')a[l][r]='-';
    else a[l][r]='+';
    // cout<<l<<' '<<r<<"\n";
    // for(int i=0;i<4;i++){
    //     cout<<a[i]<<"\n";
    // }
    // cout<<"\n";
}
void dfs(int x){
    if(x==16){//当枚举完时
        for(int i=0;i<4;i++){
            for(int j=0;j<4;j++){
                if(a[i][j]=='+'){
                    return;
                }
            }
        }
        if(ans.size()==0||v.size()<ans.size()){//找到最小的枚举次数
            ans=v;
        }
        return;
    }
    turn(x);
    v.push_back({x/4+1,x%4+1});
    dfs(x+1);//选
    v.pop_back();
    turn(x);
    dfs(x+1);//不选
}
int main(){
    for(int i=0;i<4;i++)cin>>a[i];
    dfs(0);
    cout<<ans.size()<<"\n";
    for(int i=0;i<ans.size();i++){
        cout<<ans[i].first<<' '<<ans[i].second<<"\n";
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值