基础算法思想(递归篇)

由于今天的练习计划太难了,所以我偷偷的跑去看下一周的练习题了

递归虽然做法比较暴力,但是他确实是一个必不可少的思想,而且有一些问题就用递归才更方便,他还是很多算法的基础比如搜索、动态规划、树论等等。

接下来就开始逐渐走进递归的世界吧!

全排列问题

这是最基础的递归以及回溯问题,我们可以不断的通过递归来实现“一条路走到黑”,然后再通过回溯去遍历其他的路径,由于要输出每一个排列组合,所以我们可以用一个数组将目前所选的这个元素存起来,当遍历完所有的元素的时候(num == n+1)就可以输出了,注意回溯的时候需要将当前元素的使用标记还原,用以在之后对这个元素的选择。

// Problem: P1706 全排列问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1706
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include 
using namespace std;
#define int long long 
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair
#define fi first
#define se second
int n,res[15];
bool v[15];
void dfs(int num)
{
	if(num == n+1)
	{
		for(int i=1;i<=n;i++) printf("%5lld",res[i]);
		printf("\n");
		return ;
	}
	for(int i=1;i<=n;i++)//每一位上都有n、n-1、n-2...个可能 所以要对每一个都遍历
	{
		if(!v[i])
		{
			v[i] = 1;
			res[num] = i;
			dfs(num+1);
			v[i] = 0;//将全局变量状态还原 回溯!
		}
	}
}
void pre_handle()
{
	
} 

void solve()
{
	cin>>n;
	dfs(1);
}

signed main()
{
	IOS
	int T=1;
	pre_handle();
//	cin>>T;
	while(T--) solve(); 
	return 0;
} 

kkksc03考前临时抱佛脚

这道题我一开始读不懂题,以为任务是要两个两个弄,都弄完才能处理下一组任务,其实是大脑是双核的,左脑处理完当前任务的时候就可以迅速接下一个任务!当然右脑也是。由于观察到数据范围比较小,所以就可以直接想到暴力的递归,也就是深度优先搜索去遍历每一种的可能,因为每一个任务都有“被左脑处理”和“被右脑处理”两种可能所以就要遍历所有的情况(指数级的复杂度),然后通过比较求出每一组的最小值(因为他每一组之间要分开求)最后累加即可。

// Problem: P2392 kkksc03考前临时抱佛脚
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2392
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include 
using namespace std;
#define int long long 
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair
#define fi first
#define se second
int ll,rr,ans,mini,s[5],a[22];
void dfs(int num,int f)//f代表当前处理的是第几组的任务(科目种类)便于对这一组的任务数量的调用 即s[f]
{
	if(num == s[f]+1)//如果遍历完了这一组的所有任务 即每一个任务都分配好了 就更新这一组任务的所需时间
	{
		mini = min(mini,max(ll,rr));
		return ;
	}
	ll += a[num];//还没有对所有任务进行分配,就还需要继续递归,首先是对这个任务分配给左脑
	dfs(num+1,f);
	ll -= a[num];//回溯的时候需要将分配给左脑这个状态还原
	rr += a[num];//将任务分配给右脑继续以这种状态往下递归
	dfs(num+1,f);
	rr -= a[num];//这一个任务的分配情况枚举完之后就还原状态,防止对上一级的递归造成影响
}
void pre_handle()
{
	
} 

void solve()
{
	cin>>s[1]>>s[2]>>s[3]>>s[4];
	for(int i=1;i<=4;i++)
	{
		mini = INT_MAX;//注意每组都要求最值,所以要一直更新mini
		ll = 0,rr = 0;//分别对应左脑处理任务的时间 和 右脑处理任务的时间
		for(int j=1;j<=s[i];j++) cin>>a[j];
		dfs(1,i);//通过dfs来求出每一组所需要的最小时间
		ans += mini;
	}
	cout<>T;
	while(T--) solve(); 
	return 0;
} 

PERKET

这道题看数据范围也很小,所以也就考虑用递归来枚举每一种情况喽,对于每一个原料都有选和不选两种情况,所以子问题就是枚举当前这个原料的这两种情况即可,如果选就对酸度和苦度进行计算然后往下递归枚举下一个的原料,最后不选的时候只需要将之前计算的酸度和苦度的计算还原即可(回溯)。还是因为要对每一个原料都进行选和不选的操作,所以还是需要下标参数num==n+1的时候才为终止条件,而且要在选之后更新答案防止有一个都不选的情况出现。

// Problem: P2036 [COCI 2008/2009 #2] PERKET
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2036
// Memory Limit: 32 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include 
using namespace std;
#define int long long 
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair
#define fi first
#define se second
pii a[15];
int suan=1,ku=0,n,ans = INT_MAX;
void dfs(int num)
{
	if(num == n+1) return ;
	suan *= a[num].fi;
	ku += a[num].se;
	int t = abs(suan - ku);
	ans = min(ans,t);//在选的时候更新答案防止出现不合题意的都不选的情况
	dfs(num+1);
	suan /= a[num].fi;
	ku -= a[num].se;
	dfs(num+1);
}
void pre_handle()
{
	
} 

void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i].fi>>a[i].se;
	dfs(1);
	cout<>T;
	while(T--) solve(); 
	return 0;
} 

小Y拼木棒

这个题注意到数据范围较大,如果遍历所有的木棒的话就会超时,更不用说搜索所有的木棒了,这时候可以再注意到木棒的长度的范围是允许O(N²)的时间复杂度的,所以我们可以用一个桶数组来存放所有的长度的小木棍的数量,然后因为要选四个木棒且必须以这四个木棒拼接正三角形,所以就一定有两根相等的长边和两根和为场边的短边,这时候由于是求有多少种选法,所以要用累乘计算出结果。

// Problem: P3799 小 Y 拼木棒
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3799
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include 
using namespace std;
#define int long long 
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair
#define fi first
#define se second
const int mod = 1e9+7,N = 1e5+10;
int n,ans=0;
vector v(5010);//桶数组 存长度
void pre_handle()
{
	
} 
int res(int x)
{
	int num=0;
	for(int i=1;i<=x/2;i++)//遍历每一个长度 由于是两根 所以遍历到x的一半即可
	{
		//如果有两根短边并且两根短边不相同 那么这两根短边的选法就有两者乘积种
		if(v[i] && v[x-i] && i!=x-i) num += v[i]*v[x-i];
		//而如果两根短边长度相等的话就是组合数了 [(v[i]-1)+1]*(v[i]-1)/2 = v[i]*(v[i]-1)/2;
		else if(v[i] && v[x-i] && i == x-i) num += v[i]*(v[i] - 1LL)/2LL;
	}
	num %= mod;
	//别忘记长边也有很多种的话也需要与这几个短边进行组合 长边的组合数的计算和上面的else一样
	num *= v[x]*(v[x] - 1LL)/2LL;
	return num %= mod;
}
void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int x;cin>>x;
		v[x]++;
	}
	for(int i=2;i<=5000;i++)
	{
		if(v[i]<2) continue;//如果长边的数量没有两个就continue(构不成三角形)
		ans += res(i);
		ans %= mod;
	}
	cout<>T;
	while(T--) solve(); 
	return 0;
} 

幂次方

这个题也是一道考察递归的题目,而且还需要用到分治的思想,一个数可以分为多个幂次方相加,所以要对这几个分别进行递归,根据样例的提示,我们需要从最高次幂开始往下,一旦找到比当前的x小的数了,就对这个次方进行递归终止条件就是次方等于1或0。之后如果有剩余的话就需要有一个字符‘+’来连接接下来的次方了。

// Problem: P1010 [NOIP 1998 普及组] 幂次方
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1010
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include 
using namespace std;
#define int long long 
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair
#define fi first
#define se second
int n;
void digui(int x)
{
	for(int i=14;i>=0;i--)
	{
		if(pow(2,i)<=x)
		{
			if(i==0) cout<<"2(0)";
			else if (i==1) cout<<"2";
			else
			{
				cout<<"2(";
				digui(i);
				cout<<")";
			}
			x -= pow(2,i);
			if(x) cout<<"+";
		}
	}
}
void pre_handle()
{
	
} 

void solve()
{
	cin>>n;
	digui(n);
}

signed main()
{
	IOS
	int T=1;
	pre_handle();
//	cin>>T;
	while(T--) solve(); 
	return 0;
} 

南蛮图腾

这道题是一个十分明显的递归题,要想求n大小的图形,就必须依靠n-1的图形作为基础,而所有的图形,最终都需要用n=1时的图形作为基础,对于不断的更新图形,我们可以直接用一个二维字符数组来保留图形中间变换过程,最终直接输出这个数组即可,那递归的逻辑怎么实现呢?观察规律可以发现,大小为n的图形的宽度为2^(n+1),而高度为2^(n),所以我们要知道n=1时的图形咋画,以及偏移量该怎么算出来,因为每一个图形都基于n=1的时候画,所以递归时参数应该有当前位置的坐标以及当前的n,如果当前的n为1的话就说明需要在当前的位置上进行画n=1的时候的图了,n大小的图形是由3个大小为n-1的图形构成的,所以需要递归三次分别来画,那么这三次的递归坐标参数应该是多少呢【一开始是在0,0位置处画大小为n的图形,那么画n-1的图形就要在0,0的基础上加上偏移量才行】,可以发现,大小为n的图形递归到大小为n-1的图形的时候,图形1就需要在y轴上平移大小为n时图形高度的一半(即大小为n-1的时候的图形的高度)就拿n=2和n=1来举例,一开始是在0,0的位置处画大小为2的图形,可现在只知道大小为1的时候的图形,所以就需要画三个大小为1的图形,第一个图形的x坐标没有偏转,而y坐标向右偏移了2个单位,第二个图形的y不变,x向下平移了2个单位,第三个图形的x向下平移了2个单位,y向右平移了4个单位,此时偏移量就找出来了,只需要在递归的时候将当前的位置坐标加上偏移量以及大小即可完成。

// Problem: P1498 南蛮图腾
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1498
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include 
using namespace std;
#define int long long 
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair
#define fi first
#define se second
const int N = 2050;
int n;
char mp[N][N];
void pre_handle()
{
	memset(mp,' ',sizeof(mp));
} 
void f(int x,int y,int n)//在坐标为x,y的位置画大小为n的图形!
{
	if(n==1)//递归终止条件 到1的时候就可以打印了
	{
		mp[x+1][y] = '/';
		mp[x][y+1] = '/';
		mp[x][y+2] = '\\';
		mp[x+1][y+3] = '\\';
		mp[x+1][y+1] = '_';
		mp[x+1][y+2] = '_';
		return ;
	}
	int distance = pow(2,n);//偏移量
	f(x,y + distance/2,n-1);//1号三角形(最上面)
	f(x + distance/2,y,n-1);//2号三角形(左下角)
	f(x + distance/2,y + distance,n-1);//三号三角形(右下角)
}
void solve()
{
	cin>>n;
	f(0,0,n);
	int distance = pow(2,n);//计算出图腾的高度 宽度为pow(2,n+1)!
	for(int i=0;i>T;
	while(T--) solve(); 
	return 0;
} 

外星密码

这也是一个递归的好题,难就难在会出现加密字符串复合叠加的情况,这时候如果正常遍历就很难实现了,但是如果是递归呢?在破解的时候找到一个加密串之后对剩下的进行递归破解即可,一个复合的加密串就相当于是一个新的加密串,所以还是调用解密的递归函数进行解密之后再继续往下走外层的循环即可。

// Problem: P1928 外星密码
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1928
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include 
using namespace std;
#define int long long 
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair
#define fi first
#define se second
string f(string s)
{
	string ans;
	for(int i=0;i>s;
	cout<>T;
	while(T--) solve(); 
	return 0;
} 

最后:

递归并非万能,适合解决 “结构自相似” 的问题 —— 即问题本身或其求解过程中存在重复的子结构。常见场景包括:

  • 数学定义类问题:阶乘、斐波那契数列、幂运算(a^n = a × a^(n-1))等;
  • 数据结构相关问题:树的遍历(前序 / 中序 / 后序遍历)、图的深度优先搜索(DFS)、链表逆序等;
  • 分治思想问题:快速排序(分治拆分数组)、汉诺塔问题(拆分移动步骤)等;
  • 回溯类问题:全排列、子集枚举、迷宫求解(通过递归尝试每一条路径)等。

 

你可能感兴趣的:(基础算法思想(递归篇))