---------------------------------------------------------------------------------------------------------------------------------
目录
①递归
92-递归实现指数型枚举
题目:
从 1∼n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。
输入格式:
输入一个整数 n。
输出格式:
每行输出一种方案。
同一行内的数必须升序排列,相邻两个数用恰好 1 个空格隔开。
对于没有选任何数的方案,输出空行。
本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。
数据范围:
1≤n≤15
输入样例:
3
输出样例:
3
2
2 3
1
1 3
1 2
1 2 3
分析:
DFS求解
第一步:画递归树,对于小于等于n的数,每一个数只有两种选择:选或不选,运用深搜先全部选或不选,再逐级返回输出答案
递归树:
第二步:思路转化成代码
递归的临界条件是什么?数组下标从1开始,u>n输出
需要的变量:开一个状态bool数组,来表示该数有没有被选
本题要求:只是要求同行内升序,不同行之间不做要求
代码:
#include<iostream>
using namespace std;
const int N=20;
bool state[N];
int num[N];
int n;
void dfs(int u)
{
if(u>n)
{
for(int i=1;i<=n;i++)
if(state[i]==1)
cout<<i<<" ";
cout<<endl;
return;
}
//不同行升序--先选
// //先选
// state[u]=true;
// dfs(u+1);
// //恢复现场+不选
// state[u]=false;
// dfs(u+1);
//不同行不升序--先不选
//先不选
state[u]=false;
dfs(u+1);
//选
state[u]=true;
dfs(u+1);
}
int main()
{
cin>>n;
dfs(1);
return 0;
}
---------------------------------------------------------------------------------------------------------------------------------
94-递归实现组合型枚举
题目详情:
从 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
思考题:如果要求使用非递归方法,该怎么做呢?
分析:
题目要求:n最大值能取到24,所以N开得要比24大;同行升序,不同行按对应下标升序
第一步:画递归树:
第二步:转换成代码
1、题目要求同行升序,那么我们需要设置一个状态数,记录当前枚举到哪了,再次进入递归时从记录的状态数开始枚举,可以保证同行和不同行内都为升序
2、dfs(int u,int state):一个记录下标,一个记录已经枚举到的数
3、一次递归结束条件:u>m
代码
//4.4重做
#include<iostream>
using namespace std;
const int N=20;
int num[N];
int n,m;
int flag;
void dfs(int u,int state)
{
if(u>m)
{
for(int i=1;i<=m;i++)
cout<<num[i]<<" ";
cout<<endl;
return ;
}
for(int i=state;i<=n;i++)
{
num[u]=i;
dfs(u+1,i+1);
}
}
int main()
{
cin>>n>>m;
dfs(1,1);
return 0;
}
另一种解法代码(不设置状态数):
分析
1、传入的参数即为当前枚举到哪了
2、设置一个flag,用来表示现在已经枚举了几个数了
3、一次递归结束条件变为:flag==m
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=110;
int n,m;
int flag=0;
int p[N];
void dfs(int u)
{
//边界条件-当存储的数为m时
if(flag==m)
{
for(int j=1;j<=flag;j++)
cout<<p[j]<<" ";
cout<<endl;
return ;
}
//深搜
for(int i=u;i<=n;i++)
{
p[++flag]=i;
dfs(i+1);
p[flag--]=0;//或者直接写成 flag--; 一次递归结束后恢复数据
}
}
int main()
{
scanf("%d%d",&n,&m);
dfs(1);
return 0;
}
---------------------------------------------------------------------------------------------------------------------------------
1209-带分数
题目详情:
数据范围
1≤N<10^6
分析:
1、将除法转换成除法:n=a+b/c --> n*c=a*c+b ;a为输入
2、暴搜,先用递归求出0~9的全排列,然后将求出的数通过循环分为三段分别为a,b,c;
如果满足n*c=a*c+b,则为答案
代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e6+10;
int n; //需要求的目标数
int num[10]; //存放全排列的结果
bool st[10]; //全排列记录数是否被用过
int res=0; //最终结果
int arr(int l,int r)
{
int ans=0;//划分好的答案
for(int i=l;i<=r;i++)
{
ans=ans*10+num[i];
}
return ans;
}
void dfs(int u)
{
if(u>9)
{
//暴力将每个数划分为三段
for(int i=1;i<=7;i++)
for(int j=i+1;j<=8;j++)
{
int a=arr(1,i);
int b=arr(i+1,j);
int c=arr(j+1,9);
//满足题设条件
if(n*c==a*c+b)
{
res++;
}
}
return ;
}
//对数进行全排列
for(int i=1;i<=9;i++)
{
if(!st[i])
{
st[i]=true;
num[u]=i;
dfs(u+1);
st[i]=false;
}
}
}
int main()
{
cin>>n;
dfs(1);
cout<<res;
return 0;
}
另一种解法:
利用函数:next_permutation(num+1,num+9);
对于next_permutation函数,其函数原型为:
#include <algorithm>
bool next_permutation(iterator start,iterator end)
对本题中的num[1]~num[9]进行升序排序,但注意,只能从原数组现有的序列开始升序排序,所以原数组必须初始化为num[10]={0,1,2,3,4,5,6,7,8,9}(假如下标从1开始的话)
假如传的为next_permutation(num+1,num+4),只会对1,2,3,4进行升序排序,5~9顺序不变
如图:
next_permutation用法链接https://www.cnblogs.com/kingwz/p/15187020.html
代码:
//函数全排列
#include<iostream>
#include<algorithm>
using namespace std;
int n;
int num[10];
int ans=0;
int calt(int l,int r)
{
int res=0;
for(int i=l;i<=r;i++)
{
res=res*10+num[i];
}
return res;
}
int main()
{
cin>>n;
for(int i=1;i<=9;i++) num[i]=i;
do{
for(int i=1;i<8;i++)
for(int j=i+1;j<9;j++)
{
int a=calt(1,i);
int b=calt(i+1,j);
int c=calt(j+1,9);
if(a*n==b*a+c) ans++;
}
}while(next_permutation(num+1,num+10));
cout<<ans;
}
--------------------------------------------------------------------------------------------------------------------------------
蓝桥-小朋友崇拜圈
题目详情:
班里 N 个小朋友,每个人都有自己最崇拜的一个小朋友(也可以是自己)。
在一个游戏中,需要小朋友坐一个圈,每个小朋友都有自己最崇拜的小朋友在他的右手边。
求满足条件的圈最大多少人?
小朋友编号为 1,2,3,⋯N。
输入描述
输入第一行,一个整数 N(3<N<10^5)。
接下来一行 N 个整数,由空格分开。
输出描述
要求输出一个整数,表示满足条件的最大圈的人数。
输入输出样例
示例
输入
9 3 4 2 5 3 8 4 6 9
输出
4
样例解释
如下图所示,崇拜关系用箭头表示,红色表示不在圈中。
显然,最大圈是[2 4 5 3] 构成的圈。
分析:
解题思路:暴搜+DFS
暴搜:求第1个到第N个小朋友各自形成的崇拜圈,选取最大值
DFS:具体求每个小朋友的崇拜圈的大小
love[i] -- 为第i个小朋友崇拜的人
vis[i] -- 在DFS函数中记录第i个小朋友有几个人崇拜
分析:要想形成一个崇拜圈,则每当搜索一个小朋友时,要么崇拜的他的人数为0,继续搜索
他崇拜的人,要么为1,且他崇拜的人为第一个孩子,否则无法成环。
由此可知,递归的返回条件为:
if(vis[curcd]&&curcd!=firstcd) return ;
if(vis[curcd]&&curcd==firstcd){
ans=max(ans,num);
return ;
}
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100010;
int love[N];
int vis[N];
int ans;
int n;
void dfs(int curcd,int firstcd,int num)
{
//当崇拜当前孩子的人数不为0即在之前这个孩子在环里就出现过
//如果这个孩子不是第一个孩子,说明当前这个崇拜圈已经不是一个环了
if(vis[curcd]&&curcd!=firstcd)
return ;
//当崇拜当前孩子的人数不为0即在之前这个孩子在环里就出现过
//且这个孩子是第一个孩子,那么成功组成了1个环
if(vis[curcd]&&curcd==firstcd)
{
ans=max(ans,num); //N个孩子中最大的崇拜圈
return ;
}
vis[curcd]++; //第一个孩子加是作为标志,其他孩子加是因为被崇拜过
num++;
dfs(love[curcd],firstcd,num);
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>love[i];
//以每个小盆友为首, 搜索形成的环
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
dfs(i,i,0); //当前的孩子 第一个孩子 崇拜圈的人数
}
cout<<ans;
return 0;
}
②递推
递推介绍:递推是按照一定的规律来计算序列中的每个项,通常是通过计算前面的一些项来得出序列中的指定项的值。其思想是把一个复杂的庞大的计算过程转化为简单过程的多次重复,该算法利用了计算机速度快和不知疲倦的机器特点。
717- 简单斐波那契
题目详情:
代码:
#include<iostream>
using namespace std;
const int N=1e6+10;
int f[N];
int main()
{
int n;
cin>>n;
f[0]=0;
f[1]=1;
//递推求f[i]
for(int i=2;i<n;i++) f[i]=f[i-1]+f[i-2];
for(int i=0;i<n;i++) cout<<f[i]<<" ";
return 0;
}
1208-翻硬币
题目详情:
分析:
题目要求:翻动两个相邻的硬币为一步操作
其实本题很简单,每一个硬币最多进行一步操作,如果进行两步操作相当于没动,要想达到最终结果,我们只需逐一比较
代码:
#include<iostream>
#include<cstring>
using namespace std;
int cnt=0;
int main()
{
string a,b;
cin>>a>>b;
for(int i=0;i<a.size();i++)
{
if(a[i]!=b[i])
{
if(a[i]=='*') a[i]='o';
else a[i]='*';
if(a[i+1]=='*') a[i+1]='o';
else a[i+1]='*';
cnt++;
}
}
//cout<<a<<endl<<b<<endl;
cout<<cnt;
}
95-费解的开关
题目详情
你玩过“拉灯”游戏吗?
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
分析:
首先,我们需要明确的是,当第一行唯一确定时,那么按法便唯一确定
为什么呢?我们肯定是一行一行的将灯泡变亮,第一行的灯泡只能由第二行来决定,因为一旦你动了第一行的开关,
它相邻的灯泡一定会改变,如果相邻灯泡变暗且是改行最后一个灯光,那我们就必须再按相邻的这个灯泡开关,可原来点亮的灯泡又暗了,
这样十分麻烦且不好计算,所以我们用下一行的灯泡来决定上一行的灯泡.那么问题又来了,相同的灯泡最多按一次,因为按
两次相当于没按,所以,当第一行确定时,整个开关的按法就确定了, 该行的灯泡必须由下一行相应的灯泡点亮
所以,我们只要把第一行所有的情况求出来,再针对每一种情况求解即可
总结:每一行开关的操作完全被前一行灯泡的亮灭状态所决定要解决的问题:
1.如何枚举第一行的操作 (一行5个灯光,一共有2^5-1个状态)
位运算,定义op(op<=31),判断op对应二进制的每个位置上是1还是0,如果是1,就对第一行该位置的灯泡进行操作 (如3的二进制表示00011,那么对第4和5个灯光的开关进行操作)
2.turn(x,y):灯光坐标,改变灯光及相邻灯光的状态
定义偏移量:(-1,0),(1,0),(0,0),(0,-1),(0,1),分别表示上下左右的位置
3.时间复杂度:32*25*5*500
memcpy函数用法https://www.runoob.com/cprogramming/c-function-memcpy.html
代码 :
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=10;
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;
if(g[a][b]=='0') g[a][b]='1';
else g[a][b]='0';
}
}
int main()
{
int q; //q组数据
cin>>q;
while(q--)
{
//输入一组的数据
for(int i=0;i<5;i++) cin>>g[i];
int res=100;
for(int op=0;op<32;op++)
{
//对原始数据进行备份,因为要进行32次循环每次循环需要在原始数据上操作
memcpy(backup,g,sizeof g);
int step=0; //记录步数
//对第一行的5个数据进行操作
for(int i=0;i<5;i++)
{
if(op>>i&1) //如果op二进制的第5-i位为1,就对第一行的该灯光进行操作
{
step++;
turn(0,4-i);
}
}
//如果1~4行的灯光为灭,我们需要操作一下
//即按一下下一行j列的灯光开关
for(int i=0;i<4;i++)
for(int j=0;j<5;j++)
{
if(g[i][j]=='0')
{
step++;
turn(i+1,j);
}
}
//对一组数据检测完后,检查是否灯全亮
int dark=1;
for(int i=0;i<5;i++)
if(g[4][i]=='0')
{
dark=0;
break;
}
//如果灯光全亮,更新答案
if(dark==1) res=min(res,step);
//对数据进行恢复
memcpy(g,backup,sizeof g);
}
//不可以将灯光的全亮检查放在这里
//因为这只能判断一种情况,即当op=31时对灯光进行操作的情况
//进行输出
if(res>6)
cout<<-1<<endl;
else cout<<res<<endl;
}
return 0;
}