算法与数据结构---递归

递归知识点

如何将一维数组的所有元素初始化为0?

将数组a定义成全局变量或静态变量
int a[5] = {0,0,0,0,0};
memset(a,0,sizeof(a))
int a [5]={0};

简单函数的编写

1、计算两点欧几里德距离的函数
在这里插入图片描述
下面给出上述函数的另一种方法:

在这里插入图片描述
说明:
(1)hypot函数的功能是计算一直角三角形的斜边长度。
(2)函数hypot(x,y)表示根据直角三角形的两直角边长度x和y计算其斜边的长度。或者是从标点(x,y)到原点的距离,该函数的算法等同于sqrt(xx+yy)。
(3)hypot函数头文件math.h或cmath hypot(a,b)的返回值为double类型

2、作用域与作用范围
请添加图片描述
3、指针
关于指针初学者易犯的错误:

在这里插入图片描述
它交换了swap函数的局部变量a和b(辅助变量t必须是指针。int a是错误的),但却始终没有修改它们指向的内容,因此main函数中的a和b不会改变。

在这里插入图片描述
这个程序去替换前正确程序,可能得到的结果是“4 3”。但是它还是错误的,因为t是一个变量(指针也是一个变量,只不过类型是“指针”而已),所以根据规则,它在赋值之前是不确定的。如果这个“不确定的值”所代表的内存单元恰好是能写入的,那么这个程序将正常工作;但如果它是只读的,程序可能会崩溃。
正确写法

Void swap(int *a,int *b)
{
   int t;
   t=*a;
   *a=*b;
   *b=t;
}

递归函数

6-3 求组合数(高效递归版)

请编写递归函数,求组合数。

请添加图片描述
请添加图片描述

函数原型

double Cmb(int x, int y);

说明:x 和 y 为非负整数,且 x≥y,函数值为组合数 C

裁判程序

#include <stdio.h>

double Cmb(int x, int y);

int main()
{
    int m, n;
    scanf("%d%d", &m, &n);
    printf("%.10g\n", Cmb(m, n));
    return 0;
}

/* 你提交的代码将被嵌在这里 */

要求:不要使用循环语句,不调用阶乘函数和排列数函数。找出递推公式,该函数直接调用自己求得结果。

输入样例输出样例
4 26
34 172333606220

WA代码

double Cmb(int x, int y)
{
	double m;
	if(x<0||y<0||y>x)
	{
	  return 0;
	}
	else if(y==0)
	{
		return 1;
	}
	else
	{
	   return x * Cmb( x - 1, y - 1) / y;
	}

}

分析

c(5,2)和c(5,3)答案是一样的,如果想让程序更加高效需要多加一步判断
else if(y>x/2) { y=x-y; return Cmb(x,y); }

AC代码

double Cmb(int x, int y)
{
	double m;
	if(x<0||y<0||y>x)
	{
	  return 0;
	}
	else if(y==0)
	{
		return 1;
	}
    else if(y>x/2)//高效
	{
		y=x-y;
        return Cmb(x,y);
	}
	else
	{
	   return x * Cmb( x - 1, y - 1) / y;
	}

}

6-4 判断整数N是否为素数

对于整数N(N在int范围内),如果正整数N只能被1和N整除,则N为素数。

函数接口定义:

//判断一个整数n是否为素数
int isPrime(int n);

裁判测试程序样例:

#include<stdio.h>
//判断一个数是否为素数
int isPrime(int);
int main()
{
       int m,i,k;
       while(scanf("%d",&m)==1){
                if(isPrime(m)){
                    printf("prime\n");
                }
                else{
                    printf("not prime\n");
                }
        }
       return 0;
}
// 你的代码将被嵌在这里
输入样例1:输出样例1:
11prime

代码

int isPrime(int n)
{
    int i;
    int a=1;
    if(n<=1) a=0;//此步判断不能少
    else{
        for(i=2;i<=(int)sqrt((double)n);i++){
            if(n%i==0){
                a=0;
                break;//不能少
            }
        }
    }
    return a;
}

在pta上运行此题,细节要求多。

高精度问题(传统的递推法或者递归 容易出现超时错误)——————————————————————————

# Number Sequence

题目描述

PDF

输入格式

输出格式

样例 #1

样例输入 #1

2
8
3

样例输出 #1

2
2

思路:

传统⽅法,暴⼒循环次数太⼤,超时错误

f[i]=(af[i-1]+bf[i-2])%7;

1)先输出部分数据到⽂件,打表找规律
2)分析算式特点,找规律

代码

#include<stdio.h>
int main(){
	int a,b,n;
	while(scanf("%d%d%d",&a,&b,&n)!=EOF){
		if(a==0&&b==0&&n==0) break;
		int c[50]={0};
		c[0]=1;
		c[1]=1;
		for(int i=2;i<49;i++)
			c[i] = (a*c[i-1]+b*c[i-2])%7;
		printf("%d\n",c[n%49-1]);
	}
	return 0;
}

最多只能出现7*7=49种情况

2、 D - Rightmost Digit

Given a positive integer N, you should output the most right digit of N^N.

Input

The input contains several test cases. The first line of the input is a single integer T which is the number of test cases. T test cases follow.
Each test case contains a single positive integer N(1<=N<=1,000,000,000).

Output

For each test case, you should output the rightmost digit of N^N.

Sample

inout
2
37
46

Hint

  1. In the first case, 3 * 3 * 3 = 27, so the rightmost digit is 7.
  2. In the second case, 4 * 4 * 4 * 4 = 256, so the rightmost digit is 6.

分析

个位数字只与n的个位数字有关,因此每次只要乘上n的个位数字后再对10取模即可。但是本题的n较大,如果只是普通地计算幂,会超时,需要用快速幂的算法。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
 
int fastpow (int x,ll n)
{
	if(n==1) return x;
	if(n%2==0) return (fastpow(x,n/2)*fastpow(x,n/2))%10;
	else return (x*fastpow(x,n/2)*fastpow(x,n/2))%10;
}
int main()
{	
	int a;
	ll n;
	cin>>a;
	while(a--){
		cin>>n;
		cout<<fastpow(n%10,n)<<endl;
	}
	return 0;
} 

快速幂

int fastpow (int x,ll n)
{
	if(n==1) return x;
	if(n%2==0) return (fastpow(x,n/2)*fastpow(x,n/2))%10;
	else return (x*fastpow(x,n/2)*fastpow(x,n/2))%10;
}

数塔问题

题目描述

如下所示为一个数字三角形。请编一个程序计算从顶到底的某处的一条路径,使该路径所经过的数字总和最大。只要求输出总和。
在这里插入图片描述

1、 一步可沿左斜线向下或右斜线向下走;
2、 三角形行数小于等于100;
3、 三角形中的数字为0,1,…,99;

input:

5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

output:

86

分析

此题解法有多种,从递推的思想出发,设想,当从顶层沿某条路径走到第i层向第i+1层前进时,我们的选择一定是沿其下两条可行路径中最大数字的方向前进,为此,我们可以采用倒推的手法,设a[i][j]存放从i,j 出发到达n层的最大值,则a[i][j]=max{a[i][j]+a[i+1][j],a[i][j]+a[i+1][j+1]},a[1][1] 即为所求的数字总和的最大值。

倒推

只要清楚每次走第i步是往左更优还是往右更优,需要在a[ n ][ i ] 里找路径和最大的就是答案,逆着走也一样,从下往上走,最后a[1][1] 就是答案

顺推

从上到下,每个点一定是上一层的能走的路径最大值的

倒推代码

#include <bits/stdc++.h>//数塔问题 

using namespace std;

int main()
{
	int n,a[101][101];
	cin>>n;
	for(int i = 1;i<=n;i++){
		for(int j = 1;j<=i;j++){
			cin>>a[i][j];
		}
	}
	for(int i = n-1;i>=1;i--){
		for(int j = 1;j<=i;j++){
		   //路径选择
            a[i][j] += max(a[i+1][j+1],a[i+1][j]);
		}
	}
	cout<<a[1][1];
	
	return 0;
}

顺推代码

#include<bits/stdc++.h>

using namespace std;

const int N=2000;

int a[N][N];

int main()
{
	int n;
	cin>>n;
	int maxx = 0;//方便后面判断最大路径
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			cin>>a[i][j];//输入数据 
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
		a[i][j] += max(a[i-1][j],a[i-1][j-1]);//每一步都选择相对来说大的数值
		 
		}
	}  
	for(int i=1;i<=n;i++){
		maxx = max(a[n][i],maxx);
	} 
	cout<<maxx;
	
    return 0;
}


昆虫繁殖【这题有点点难哦】

题目描述

科学家在热带森林中发现了一种特殊的昆虫,这种昆虫的繁殖能力很强。每对成虫过x个月产y对卵,每对卵要过两个月长成成虫。假设每个成虫不死,第一个月只有一对成虫,且卵长成成虫后的第一个月内不产卵(过X个月产卵).问过Z个月以后,共有成虫多少对?

1=<x<=20,1<=y<=20,x=<z<=50

分析

  1. 本月成虫的数量=前2个月卵的数量+前1个月成虫的数量
  2. 本月卵的数量=前x月成虫的数量*y

设f[i]表示第 i 月昆虫成虫的数量,b[i]表示第 i 月的新虫卵的数目

b[i] = f[ i - x] * y; (成虫经过x月产卵 y对)

f[i] = f[ i - 1] + b[ i - 2];(卵经过2个月长成成虫)

F[1]=1 b[1]=0

代码

#include <bits/stdc++.h>//昆虫繁殖

using namespace std;

//const int N = 1e6 + 10;
typedef long long ll; 

int main()
{
	ll x,y,z,a[105]/*成虫的数量*/,b[105]/*卵的数*/;
	cin>>x>>y>>z;
	for(int i=1;i<=x;i++){//在x个月之内成虫都是,都不产卵,所以赋初值一定是用循环
		a[i] = 1;//根据题意,第1个月只有一对成虫 
		b[i] = 0;//不产卵 
	}
	for(int i = x+1;i<=z+1;i++){//因为要统计到第z个月后,所以要for到z+1
		b[i] = y*a[i-x];// i-x:此时对应x月之前的卵 
		a[i] = a[i-1] + b[i-2];//上个月的成虫 + 新长成的 
	}
	cout<< a[z+1]<<endl;
	return 0;
}

位数问题

题目描述

在所有的n位数中,有多少个数中有偶数个数字3 ? (说明,0是偶数)

分析 (注意不能有前导0)

even[i]表示前i位中有偶数个3的数目

  • 前i-1位有偶数个3,第i位不能为3

  • 前i-1为有奇数的3,第i位是3

even[i] = even[i-1][0] * 9 + odd[i-1][1]【乘以9是因为,第i位除了3以外的每一个数字】

odd[i]表示前i位中有奇数个3的数目

同理,odd[i] = odd[i-1] * 9 + even[i-1]

先考虑 n=1 的情况:

odd[1] = 1;

1位数中有奇数个3的1个,只有3

even[1] =8;

1位数中有偶数个3的有8个,因为能放 1,2 ,4,5,6,7,8,9在第一位上

再向 n>=2 情况递推

偶数个 3的个数,就是奇数个 3的个数,末尾加上一个3 + 偶数个3 的个数,末尾加上非3 的数
奇数个 3的个数,就是偶数个 3的个数,末尾加上 3的数 + 末尾加上一个3 + 偶数个 3的个数,
**注意:**对n=1 的询问情况要特判,如果只有 1位的话,那么0 是可以放在第 1 位的

代码

#include <bits/stdc++.h>//位数问题

using namespace std;

const int mod = 12345;
const int N = 1010;


int main()
{
	int n;
	cin>>n;
	int even[N],odd[N];
	//n大于等于2位时,0不能放在第一位上,所以只有八种
	even[1] = 8;
	odd[1] = 1;
	if(n==1){//n为1时单独处理
		cout<<9;
		return 0;
	}
    for(int i=2;i<=n;i++){
    	
		even[i] = even[i-1]*9 % mod + odd[i-1] % mod;
		odd[i] = odd[i-1]*9 % mod + even[i-1] % mod;
	}
	cout<<even[n] <<endl;

	 
	return 0;
}

【例6】过河卒(Noip2002)

这道题的数据可能很大,注意要开long long

问题描述

棋盘上A点有一个过河卒,需要走到目标B点。卒行走的规则:可以向下、或者向右。同时在棋盘上的任一点有一个对方的马(如C点),该马所在的点和所有跳跃一步可达的点称为对方马的控制点,如图3-1中的C点和P1,……,P8,卒不能通过对方马的控制点。棋盘用坐标表示,A点(0,0)、B点(n, m) (n,m为不超过20的整数),同样马的位置坐标是需要给出的,C≠A且C≠B。现在要求你计算出卒从A点能够到达B点的路径的条数。
在这里插入图片描述

分析(本文图片引用自“kcfzyhq”的博客)

  1. 首先我们来看看下面这个图,这个图基本表现了题目的意思:一个卒要从图的左上角A点走到右下角B点,而其中有一点C为马的位置,C与其周边马能走到的P1~P8点共9个点是不能走的,问有多少种从A走到B的方法

在这里插入图片描述

  1. 我们可以先把这个问题当数学问题来考虑相信许多朋友以前都遇到过类似的数学问题,对于点[i,j],它的走法数等于它上方点与其左方点走法数之和(因为只能向下或向右走),也就是B[i,j]=B[i-1][j]+b[i][j-1],如下图就是一个例子

在这里插入图片描述

  1. 但换到有马阻拦的问题中,单纯地这样搜索就行不通了,如下图
    在这里插入图片描述

  2. 这张图所得的答案虽然是正确的,但实际上这样的操作是错误的,图中蓝色的“1”应该改为0,如下图

在这里插入图片描述

  1. 因为这个位置后面被马头挡住,自然是行不通的,值应为0,在这个例子中这里的值是1还是0对答案没有影响,但大家可以想象,如果在最左边一条边上,一个点上下都是马能走到的位置,值还为1的话,就会影响它右侧点的值(不懂的看下面这个例子)
    在这里插入图片描述
  2. 就像上图,正常情况下红色点所在的一整列初始赋值都是1,但是红色点实际值应该为0,如果值仍赋为1,则会导致蓝色点的值比实际值大1,从而导致整个结果错误。
    因此,我们在赋初值时,要专门考虑最上和最左一列的情况,具体方法参见代码

代码

#include <iostream>//过河兵
#include <cstdio>
using namespace std;

long long B[21][21];
int n,m,a,b;
int dx[8] = {-1, -2, -2, -1, 1, 2, 2, 1};
int dy[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
void init()
{
    for (int i=0;i<=n;i++){ //先把所有点都赋为1,刚刚讲的特殊情况下面再考虑 
        for (int j=0;j<=m;j++){
            B[i][j]=1;
        }
    }
     //把马的位置和所有马能走到的位置都赋为0,注意考虑边界 
        B[a][b]=0;
	for (int i = 0; i < 8; i++) {//把马能控制到的范围记成-1
		int x1 = a + dx[i];
		int y1 = b + dy[i];
		if (x1 < 0 || x1 > n || y1 < 0 || y1 > m) continue;
		B[x1][y1] = 0;
	}
}

int main()
{
    scanf("%d%d%d%d",&n,&m,&a,&b);
    init();
    for (int i=0;i<=n;i++){
        for (int j=0;j<=m;j++){
            if (B[i][j]!=0){
                if (i==0 && j==0){
                    continue;
                }/*这里就是处理所说的特殊情况,相当于如果在最上一行或者最左一行 
                   出现一个马,那么后面的值都赋为0 */ 
                else if (i==0){ 
                    B[i][j]=B[i][j-1]; 
                }else if (j==0){
                    B[i][j]=B[i-1][j];
                }else{
                    B[i][j]=B[i-1][j]+B[i][j-1];
                }
            }
        }
    }
    printf("%lld\n",B[n][m]);
}

邮票问题

题目描述

设有已知面额的邮票m种,每种有n张,用总数不超过n张的邮票,能从面额1开始,最多连续组成多少面额。(1≤m≤100,1≤n≤100,1≤邮票面额≤255)

输入

第一行:m,n的值,中间用一空格隔开。

第二行:A[1…m](面额),每个数中间用一空格隔开。

输出

连续面额数的最大值

样例输入

3 4
1 2 4

样例输出

14

分析

题意:给了m 种不同面额的邮票,总共最多用 n张,求能组成的连续面额中的最大面额
请添加图片描述

代码

#include <bits/stdc++.h>
#define int long long
#define inf 0x3f3f3f
using namespace std;
int yp[256]; // 邮票面额
int dp[25601]; // dp[i]代表 i元钱需要的最少张数
signed main()
{
     int m, n;
     cin >> m >> n;
     memset(dp, inf, sizeof(dp));//注意细节要初始化每张邮票都是一个很大的值,这样后面就可以取最小值
     for (int i = 1; i <= m; i++)
     {
       cin >> yp[i];
       dp[yp[i]] = 1; // 该面额只需要1张,亦为递推的临界值
     }
     int i;
      for (i = 1;; i++) // 1开始分析
      {
         for (int j = 1; j <= i; j++)
         {
            dp[i] = min(dp[i], dp[j] + dp[i - j]);
          }
           if (dp[i] > n || dp[i] == inf)
           break; // 超出n张邮票,组不成
      }
     cout << i - 1 << endl;
     return 0;
}

竞赛中的无穷大与无穷小


平面分割

题目描述

同一平面内有n(n≤500)条直线,已知其中p(p≥2)条直线相交于同一点,则这n条直线最多能将平面分割成多少个不同的区域?

输入

两个整数n(n≤500)和p(2≤p≤n)

输出

一个正整数,代表最多分割成的区域数目

样例输入

12 5

样例输出

73

分析

详细分析

代码

#include<iostream>
using namespace std;
int main()
{
  
	int n,p;
	cin>>n>>p;
  //创建变量a,用于累加分割区域
	int a=0;
  //a加上p条线可以分割为p*2个面
	a += p*2;
  //for循环,从第p+1条线进行分割,到第n条线结束
	for(int i=p+1;i<=n;i++)
	{
    //第几条线就可以加上几个面
		 a += i;
	}
  //输出最多有几个区域
	cout<<a<<endl;
	return 0;
}


极值问题

题目描述

已知m、n为整数,且满足下列两个条件: ① m、n∈{1,2,…,k},即1≤m,n≤k ②(n2-m*n-m2)^2=1 你的任务是:编程输入正整数k(1≤k≤109),求一组满足上述两个条件的m、n,并且使m2+n2的值最大。例如,从键盘输入k=1995,则输出:m=987 n=1597。

输入样例#1:

1995

输出样例#1:

m=987
n=1597

分析

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
 
int k;
 
int main(){
	scanf("%d",&k);
	
	int f1=1,f2=1,f3=2;
	while(f3<=k){
		f1=f2,f2=f3,f3=f1+f2;
	}
	printf("m=%d\nn=%d",f1,f2);
	
	return 0;
} 

汉诺塔(Tower of Hanoi)问题

移动规则:

每次只能移动一个圆盘;圆盘可以插在X、 Y和Z中的任何一个塔座上;任何时刻都不能将一个较大的圆盘压在较小的圆盘之上

分析

假设移动n个盘子从A到C需要移动的次数为f(n)

f(0)=0

f(1)=1 临界条件

f(n)=f(n-1)+1+f(n-1)=2*f(n-1)+1 递推式【把n-1从A->B ,把第n个从A->C,把n-1从B->C】

代码

#include<iostream>
 
using namespace std;
 
int main()
 
{
 
    int f[101],n;
 
    cin>>n;
    f[0]=0;f[1]=1;

    for(int i=2;i<=n;i++)
 
        f[i]=2*f[i-1]+1 ;
 
    cout<<f[n]<<endl;
 
    return 0;
 
}

Fib数列

题目描述

Fibonacci数列的递推公式为:Fn=Fn-1+Fn-2,其中F1=F2=1。

当n比较大时,Fn也非常大,现在我们想知道,Fn除以10007的余数是多少。

输入

多组样例输入

每行输入一个数字n

输出

输出第n个fibonacci数字对10007取模

样例输入

1
2
3

样例输出

1
1
2

提示

1≤n≤1,000,000

分析

用int数组求的时候需要取模处理下,防止爆 int ,可以直接用 long long 数组
超时是因为,必须用 scanf 读入

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6+10;
ll f[N];
int main()
{
	f[1]=1;
	f[2]=1;
	for (int i = 3; i < N; i++){
		f[i]=(f[i-1]%10007+f[i-2]%10007)%10007;
    }
	ll n;
	while(~scanf("%lld",&n)){
		printf("%lld\n",f[n]);
	}
	return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值