> 文档中心 > 「蓝桥杯」

「蓝桥杯」


蓝桥杯系列文章目录

  • [ ★] 第一讲 递推与递归

题目目录:

  • [ ★] 第一讲 递推与递归
    • [ ★] 第一题 DFS下的全排列(一题带你搞懂全排列,下面第二种方法作为福利提供给大家,仅此模版,一次搞定全排列)
    • [ ★] 第一讲 递推与递归(一题带你入门开关问题)

我的核心技术者社区

诚邀各位技术爱好者,在本社区,我会尽全力给大家提供一个友好的交流平台。欢迎各位技术爱好者加入。
链接:

https://bbs.csdn.net/forums/CoreTechnologyProvider_TheOne?category=0

一、DFS下的全排列

把 1∼n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。输入格式一个整数 n。输出格式按照从小到大的顺序输出所有方案,每行 1 个。首先,同一行相邻两个数用一个空格隔开。其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。数据范围1≤n≤9输入样例:3输出样例:1 2 31 3 22 1 32 3 13 1 23 2 1

第一题代码如下:


C++版本1.0
在这里插入图片描述

#includeusing namespace std;const int N = 10;int state[N];bool used[N];int n;void dfs(int u)//u代表当前位置{if (u > n)//BaseCase,如果当前位置超出所给数字n,也就是超越边界{for (int i = 1; i <= n; i++){cout << state[i] << " ";//输出状态数组}cout << endl;return;}for (int i = 1; i <= n; i++){if (!used[i])//当前数字未被应用{state[u] = i;//当前位置为u的状态数组元素应用数字iused[i] = true;//标记当前数字已被应用dfs(u + 1);//进入下一个位置state[u] = 0;//恢复现场,位置为u的当前状态数组位置设置为0used[i] = false;//恢复现场,当前数字数字标记为未被应用}}}int main(){    cin>>n;    dfs(1);    return 0;}

JAVA版本1.0
在这里插入图片描述

import java.util.*;public class Main {    public static boolean[] used = new boolean[10];    public static int n;    public static void main(String[] args) { Scanner sc = new Scanner(System.in); n = sc.nextInt(); String[] arr = new String[n + 1]; for(int i = 1; i <= n; i++) {     arr[i] = String.valueOf(i); } dfs(1, arr);    } public static void dfs(int u, String[] state)//u代表当前位置    { if (u > n)//BaseCase,如果当前位置超出所给数字n,也就是超越边界 {     for (int i = 1; i <= n; i++)     {  System.out.print(state[i] + " ");//输出状态数组     }     System.out.println();     return; } for (int i = 1; i <= n; i++) {     if (!used[i])//当前数字未被应用     {  state[u] = String.valueOf(i);//当前位置为u的状态数组元素应用数字i  used[i] = true;//标记当前数字已被应用  dfs(u + 1, state);//进入下一个位置  state[u] = "0";//恢复现场,位置为u的当前状态数组位置设置为0  used[i] = false;//恢复现场,当前数字数字标记为未被应用     } }    }}

看到这里的朋友会说你写的两个数组是什么鬼东西,有没有更容易理解的。好的,更容易理解的代码如下。


C++版本2.0
友友们,我写了,编译器说我 Segmentation Fault
在这里插入图片描述

#includeusing namespace std;int n;vector<string> removeIndexString(vector<string> arr, int index){vector<string> x;for (int i = 0; i < n ; i++){if (i != index){x.push_back(arr[i]);}}return x;}vector<string> process(vector<string> strs){vector<string> d;if (strs.size() == 0){d.push_back("");return d;}for (int i = 0; i < strs.size(); i++){string first = strs[i];vector<string> nexts = removeIndexString(strs, i);vector<string> next = process(nexts);for (string cur : next){d.push_back((first + cur));}}return d;}int main(){cin >> n;vector<string> arr;for (int i = 0; i < n; i++){arr.push_back((i + 1) + "");}vector<string> ans = process(arr);for (string res : ans){for (int i = 0; i < res.size(); i++){cout << res[i] << " ";}cout << endl;}}

JAVA版本2.0
在这里插入图片描述

import java.util.*;public class Main {    public static void main(String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); String[] arr = new String[n]; for(int i = 0; i < n; i++) {     arr[i] = String.valueOf(i + 1); } List<String> ans = process(arr); for(String res : ans) {     char[] an = res.toCharArray();     for(int i = 0; i < an.length;i++)     {  System.out.print(an[i] + " ");     }     System.out.println(); }     }    public static List<String> process(String[] strs)    { List<String> ans = new ArrayList<>();//定义一个链表 if(strs.length == 0)//如果strs数组中没有元素了,返回空 {     ans.add("");     return ans; } for(int i = 0; i < strs.length; i++) {     String first = strs[i];//定义第一位置的元素     String[] nexts = removeIndexString(strs, i);//你把除了第一位置的元素数组给我     List<String> next = process(nexts);//你去把之后位置的元素给我     for(String cur : next)//组合,将每个位置的元素进行组合     {  ans.add(first + cur);     } } return ans;//返回答案链表    }    public static String[] removeIndexString(String[] arr, int index)//移除index位置的元素后的数组    { int N = arr.length; String[] ans = new String[N - 1]; int ansIndex = 0; for(int i = 0; i < N; i++) {     if(i != index)     {  ans[ansIndex++] = arr[i];     } } return ans;    }}

二、递推之飞行兄弟

递推之飞行兄弟,对于开关问题,首先确定所有可能性,然后尝试枚举每一种可能性。


“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有 16 个把手的冰箱。已知每个把手可以处于以下两种状态之一:打开或关闭。只有当所有把手都打开时,冰箱才会打开。把手可以表示为一个 4×4 的矩阵,您可以改变任何一个位置 [i,j] 上把手的状态。但是,这也会使得第 i 行和第 j 列上的所有把手的状态也随着改变。请你求出打开冰箱所需的切换把手的次数最小值是多少。输入格式输入一共包含四行,每行包含四个把手的初始状态。符号 + 表示把手处于闭合状态,而符号 - 表示把手处于打开状态。至少一个手柄的初始状态是关闭的。输出格式第一行输出一个整数 N,表示所需的最小切换把手次数。接下来 N 行描述切换顺序,每行输出两个整数,代表被切换状态的把手的行号和列号,数字之间用空格隔开。注意:如果存在多种打开冰箱的方式,则按照优先级整体从上到下,同行从左到右打开。数据范围1≤i,j≤4输入样例:-+-----------+--输出样例:61 11 31 44 14 34 4

第二题代码如下:


C++版本1.0

#include#define x first#define y secondusing namespace std;const int N = 5;char g[N][N], backup[N][N];//数组元素位置如下图进行标记//[0,  1,  2,   3]//[4,  5,  6,   7]//[8,  9,  10, 11]//[12, 13, 14, 15]int get(int x, int y)//获取坐标为(x,y)的数组元素所在的位置{    return x * 4 + y;}void turnOne(int x, int y)//将坐标为(x, y)的元素进行反转{    if (g[x][y] == '+') g[x][y] = '-';    else g[x][y] = '+';}void turnAll(int x, int y)//因为反转一个开关,会把该位置开关所在行和所在列全部进行反转{    for (int i = 0; i < 4; i++)    { turnOne(x, i); turnOne(i, y);    }    turnOne(x, y);//因为坐标为(x,y)的开关被反转了两次,相当于没有反转,所以在反转一次}int main(){    for (int i = 0; i < 4; i++) cin >> g[i];//输入给定的4X4的矩阵    vector<pair<int, int>> res;//定义答案数组    for (int op = 0; op < 1 << 16; op++)//因为有16个开关,每个开关有打开和关闭两个选择,所以全部的可能性为2^16 - 1    { vector<pair<int, int>> temp;//定义辅助数组 memcpy(backup, g, sizeof g);//将g矩阵进行拷贝,因为每一次操作之后需要复原,方便进行下一次可能性 for (int i = 0; i < 4; i++)     for (int j = 0; j < 4; j++)  if (op >> get(i, j) & 1)//模拟每种可能性,1代表打开,0代表关闭  {      temp.push_back({ i, j });//将坐标为(i,j)的开关打开      turnAll(i, j);//反转坐标为(i,j)的开关所在行和所在列  } bool isClosed = false;//定义标记isClosed,用来判断所有元素是否全部打开,即所有元素全部为‘-’ for (int i = 0; i < 4; i++)     for (int j = 0; j < 4; j++)  if (g[i][j] == '+')      isClosed = true; if (isClosed == false)//如果所有开关全部打开 {     if (res.empty() || res.size() > temp.size())//如果答案数组为空,或者答案数组的元素个数大于临时数组的元素个数,证明答案数组的结果不是最优解,进行替换     {  res = temp;     } } memcpy(g, backup, sizeof g);    }    cout << res.size() << endl;//输出答案数组元素个数    for (auto op : res) { cout << op.x + 1 << ' ' << op.y + 1 << endl;//因为我们是从零下表开始,答案是从一下标开始,所以将答案数组中的下标全部加一即可    }    return 0;}

JAVA版本1.0

import java.util.ArrayList;import java.util.List;import java.util.Scanner;public class tst {//数组元素位置如下图进行标记//[0,  1,  2,   3]//[4,  5,  6,   7]//[8,  9,  10, 11]//[12, 13, 14, 15]    public static class Pair{//定义Pair类,用来记录每个开关的位置信息 int x; int y; public Pair(int o1, int o2) {     this.x = o1;     this.y = o2; }    }    public static int get(int x,int y)//获取坐标为(x,y)的数组元素所在的位置    { return x * 4 + y;    }    public static void turnOne(char[][] ch, int x, int y)//将坐标为(x, y)的元素进行反转    { ch[x][y] = ch[x][y] == '+' ? '-' : '+';    }    public static void turnAll(char[][] ch, int x, int y)//因为反转一个开关,会把该位置开关所在行和所在列全部进行反转    { for(int i = 0; i < 4; i++) {     turnOne(ch, x, i);     turnOne(ch, i, y); } turnOne(ch, x, y);//因为坐标为(x,y)的开关被反转了两次,相当于没有反转,所以在反转一次    }    public static void copyArray(char[][] ch, char[][] help)    { for(int i = 0; i < ch.length; i++) {     for(int j = 0; j < ch[0].length; j++)     {  help[i][j] = ch[i][j];     } }    }    public static void main(String[] args)    { Scanner sc = new Scanner(System.in); char[][] ch = new char[4][4]; char[][] help = new char[4][4]; List<Pair> ans = new ArrayList<>();//定义答案链表 int allOp = 1 << 16;//因为有16个开关,每个开关有打开和关闭两个选择,所以全部的可能性为2^16 - 1 for(int i = 0; i < 4; i ++) {     String str = sc.next();     for(int j = 0; j < 4; j++)     {  ch[i][j] = str.charAt(j);     } } for(int op = 0; op < allOp; op++) {     List<Pair> res = new ArrayList<>();//定义辅助链表     copyArray(ch, help);//将g矩阵进行拷贝,因为每一次操作之后需要复原,方便进行下一次可能性     for(int i = 0; i < 4; i++)     {  for(int j = 0; j < 4; j++)  {      if(((op >> get(i, j)) & 1) == 1)//模拟每种可能性,1代表打开,0代表关闭      {   res.add(new Pair(i, j));//将坐标为(i,j)的开关打开,并加入临时链表   turnAll(ch, i, j);//反转坐标为(i,j)的开关所在行和所在列      }  }     }     boolean isClosed = false;//定义标记isClosed,用来判断所有元素是否全部打开,即所有元素全部为‘-’     for(int i = 0; i < 4; i ++)     {  for(int j = 0; j < 4; j++)  {      if(ch[i][j] == '+')      {   isClosed = true;      }  }     }     if(isClosed == false)//如果所有开关全部打开     {  if(ans.isEmpty() || (ans.size() > res.size()))//如果答案数组为空,或者答案数组的元素个数大于临时数组的元素个数,证明答案数组的结果不是最优解,进行替换  {      ans = res;  }     }     copyArray(help,ch); } System.out.println(ans.size());//输出答案链表元素个数 for(Pair op : ans) {     System.out.println((op.x+1) + " "+ (op.y+1));//因为我们是从零下表开始,答案是从一下标开始,所以将答案链表中的下标全部加一即可 }    }}

尾语

抱歉,我最近有些忙,博客会陆续补上。谢谢大家!