递归递推问题(DP?)

骨牌铺法

题目描述

有1×n的一个长方形,用一个1×1、1×2和1×3的骨牌铺满方格。例如当n=3时为1×3的方格。此时用1×1、1×2和1×3的骨牌铺满方格,共有四种铺法。
输入
输入整数n
输出
输出铺法总数
样例输入
3
样例输出
4
提示
n <= 40

思路

考虑递归超时的情况,用递推解题。我们可以根据最后一块骨牌的长度思考:
1.如果最后一块骨牌是 1×1,那么前 i-1 个格子必须有 dp[i-1] 种铺法。
2.如果最后一块骨牌是 1×2,那么前 i-2 个格子必须有 dp[i-2] 种铺法(前提是 i >= 2)。
3.如果最后一块骨牌是 1×3,那么前 i-3 个格子必须有 dp[i-3] 种铺法(前提是 i >= 3)。
因此,状态转移方程表示为:dp[i]=dp[i-1]+dp[i-2]+dp[i-3]
初始条件:dp[0]=1:长度为0,不铺任何骨牌;dp[1]=1:长度为1,只铺1×1;dp[2]=2,可铺1块1×2,也可铺2块1×1。

代码

#include <bits/stdc++.h>
using namespace std;
int dp[45];
int pu(int n){
	int i;
	dp[0]=1;
	dp[1]=1;
	dp[2]=2;
	for(i=3;i<=n;++i)
		dp[i]=dp[i-3]+dp[i-2]+dp[i-1];
	return dp[n];
}

int main(){
	int n;
	cin>>n;
	cout<<pu(n);
	return 0;
}

数塔问题(DP模板)

题目描述

设有一个三角形的数塔,顶点为根结点,每个结点有一个整数值。从顶点出发,可以向左走或向右走,如图所示:
在这里插入图片描述

   若要求从根结点开始,请找出一条路径,使路径之和最大,只要输出路径的和。

输入
第一行为n(n<10),表示数塔的层数
从第2行至n+1行,每行有若干个数据,表示数塔中的数值。
输出
输出路径和最大的路径值。
样例输入
5
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11
样例输出
86

思路

递推,每次更新DP数组。先把数组中所有的元素存入DP数组,然后由下至上,从n-2层开始,更新DP数组为此处元素与下方相邻两元素中较大的max的和,一直更新到第0层(即最顶层)。路径和最大的路径值即为第0层唯一的元素dp[0][0]。

代码

#include <bits/stdc++.h>
using namespace std;
int a[15][15],dp[30][30];

int main(){
	int n,i,j;
	cin>>n;
	for(i=0;i<n;++i){
		for(j=0;j<=i;++j){
			cin>>a[i][j]; 
		}
	}
	for(i=0;i<n;++i)
		dp[n-1][i]=a[n-1][i];
	for(i=n-2;i>=0;--i){
		for(j=0;j<=i;++j){
			dp[i][j]=a[i][j]+max(dp[i+1][j],dp[i+1][j+1]);
		}
	}
	cout<<dp[0][0];
	return 0;
}

平面分割

题目描述

同一平面内有n(n≤500)条直线,已知其中p(p≥2)条直线相交于同一点,则这n条直线最多能将平面分割成多少个不同的区域?
输入
两个整数n(n≤500)和p(如果n>=2则2≤p≤n)。
输出
一个正整数,代表最多分割成的区域数目。
样例输入
12 5
样例输出
73

思路

p条相交直线,组成2×p个平面;从i=p+1条直线开始递推,每增加一条直线,最大可增加i个平面。

代码

#include <bits/stdc++.h>
using namespace std;
int a[1000];

int main(){
	int i,n,p;
	cin>>n>>p;
	a[p]=2*p;
	for(i=p+1;i<=n;++i)
		a[i]=a[i-1]+i;
	cout<<a[n];
	return 0;
}

过河卒(DP)

题目描述

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

思路

定义一个int类型的dp二维数组记录路径条数,一个bool类型的ma二维数组记录马可以走的位置(卒不能走的位置),状态转移方程为:dp[i][j]=dp[i][j-1]+dp[i-1][j]
将图中马可以走与马走过的位置的ma[i][j]定义为True,其余为False;考虑下标越界的情况,将整个棋盘向右和向下各移动一格(即保证数组下标永远>=0),初始位置从原点(0,0)变为(1,1),即初始条件为:dp[1][1]=1。注:输入点B、点C的坐标也要相应移动。从(1,1)到点B,遍历每个坐标点,如果这个位置卒可以走,并且它不是起点(1,1),即可用状态转移方程更新此处dp数组的值。结果输出点B处dp数组的值即可。

代码

#include <bits/stdc++.h>
using namespace std;
long long dp[50][50];
bool ma[25][25];


int main(){
	int a,b,c,d,i,j;
	cin>>a>>b>>c>>d;
	dp[1][1]=1;
	a++;b++;c++;d++;
	ma[c][d]=true;
	ma[c-2][d-1]=true;
	ma[c+2][d+1]=true;
	ma[c-2][d+1]=true;
	ma[c+2][d-1]=true;
	ma[c-1][d-2]=true;
	ma[c-1][d+2]=true;
	ma[c+1][d-2]=true;
	ma[c+1][d+2]=true;
	for(i=1;i<=a;++i){
		for(j=1;j<=b;++j){
			if(!ma[i][j]&&(i!=1||j!=1))
				dp[i][j]=dp[i-1][j]+dp[i][j-1];
		}
	}
	cout<<dp[a][b];
	return 0;
}

昆虫繁殖

题目描述

科学家在热带森林中发现了一种特殊的昆虫,这种昆虫的繁殖能力很强。每对成虫过x个月产y对卵,每对卵要过两个月长成成虫。假设每个成虫不死,第一个月只有一对成虫,且卵长成成虫后的第一个月不产卵(过X个月产卵),问过Z个月以后,共有成虫多少对?0=<X<=20,1<=Y<=20,X=<Z<=50
输入
x,y,z的数值
输出
过Z个月以后,共有成虫对数
样例输入
1 2 8
样例输出
37

思路

定义两个数组,sum数组表示成虫个数,l数组表示卵的个数。对于前x-1个月,只有一只成虫且成虫不产卵,sum数组恒为1,l数组恒为0;第x与x+1个月,仍只有原来的一直成虫,即sum[x+1]=1,sum[x]=1,但成虫开始产y对卵,因此l[x+1]=y,l[x]=y;从第x+2个月到第z个月,成虫的个数等于一个月前的成虫个数+两个月前的卵数,卵的个数等于x个月前的成虫个数×y,依次递推。注:数据范围要开long long

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+9;
long long sum[N],l[N];

int main(){
	int x,y,z,i;
	cin>>x>>y>>z;
	for(i=1;i<=x;++i)
		sum[i]=1;
	sum[x+1]=1;
	l[x]=y;
	l[x+1]=y;
	for(i=x+2;i<=z;++i){
		sum[i]=sum[i-1]+l[i-2];
		l[i]=sum[i-x]*y;
	}
	cout<<sum[z];
	return 0;
}

位数问题

题目描述

在所有的N位数中,有多少个数中有偶数个数字3?由于结果可能很大,你只需要输出这个答案对12345取余的值。
输入
读入一个数N
输出
输出有多少个数中有偶数个数字3。
样例输入
2
样例输出
73
提示
1<=N<=1000
在所有的2位数字,包含0个3的数有72个,包含2个3的数有1个,共73个

思路

定义两个数组a,b,a数组用来计含偶数个3数的个数,b数组用来计含奇数个3数的个数。
如果n=1,那么0~9中除了3,都有偶数个3(0个3),共9个,因此初始化a[1]=9,b[1]=1;如果n>=2,从n=2开始到n-1,因为增加位数相当于在原有数前添加数字(奇数加奇数为偶数,奇数加偶数为奇数,偶数加偶数为偶数),a[n]=a[i-1]×9+b[i-1],b[n]=a[i-1]+b[i-1]×9,不断更新a,b数组,同时对12345取模。
对于第n次更新,只更新待求数组a即可,此时a[n]=a[i-1]×8+b[i-1](最高位不为0,故少一种情况),对12345取模。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+9;
int a[N],b[N];
int main(){
	int n,i;
	cin>>n;
	a[1]=9;
	b[1]=1;
	for(i=2;i<n;++i){
		a[i]=(a[i-1]*9+b[i-1])%12345;
		b[i]=(b[i-1]*9+a[i-1])%12345;
	}
	a[n]=(a[i-1]*8+b[i-1])%12345;
	cout<<a[n];
	return 0;
} 

数的计数

题目描述

我们要求找出具有下列性质数的个数(包含输入的自然数n):
先输入一个自然数n(n≤1000), 然后对此自然数按照如下方法进行处理:
1.不作任何处理;
2.在它的左边加上一个自然数,但该自然数不能超过原数(输入的n)的一半;
3.加上数后,继续按此规则进行处理,直到不能再加自然数为止。
输入
输入一个自然数n(n≤1000)
输出
输出满足条件的数的个数
样例输入
6
样例输出
6
提示
满足条件的数为 6 (此部分不必输出)
16
26
126
36
136

思路

定义一个数组a计数,进行递推。a[i]为数字经过处理后可以生成的数字个数,数字n左面可以添加1,2,······,n/2;即a[n]=a[1]+a[2]+······+a[n/2]。a[1]左不能添加数字,即a[1]=1。由上式推出:a[n-1]=a[0]+a[1]+a[2]+······+a[(n-1)/2]
1.如果n是奇数,那么(n-1)/2=n/2,a[n-1]=a[0]+a[1]+a[2]+······+a[n/2],a[n]=a[n-1]
2.如果n是偶数,那么(n-1)/2=n/2-1,a[n-1]=a[0]+a[1]+a[2]+······+a[n/2-1],a[n]=a[n-1]+a[n/2]

代码

#include <bits/stdc++.h>
using namespace std;
int a[10005];
int main(){
	int n,i;
	cin>>n;
	a[1]=1;
	for(i=2;i<=n;++i){
		a[i]=a[i-1];
		if(i%2==0)
			a[i]+=a[i/2];
	}
	cout<<a[n];
	return 0;
}

火车站

题目描述

火车从始发站(称为第1站)开出,在始发站上车的人数为a,然后到达第2站,在第2站有人上、下车,但上、下车的人数相同,因此在第2站开出时(即在到达第3站之前)车上的人数保持为a人。从第3站起(包括第3站)上、下车的人数有一定规律:上车的人数都是前两站上车人数之和,而下车人数等于上一站上车人数,一直到终点站的前一站(第n-1站),都满足此规律。现给出的条件是:共有N个车站,始发站上车的人数为a,最后一站下车的人数是m(全部下车)。试问x站开出时车上的人数是多少?
输入
a(≤20),n(≤20),m(≤2000),和x(≤20),
输出
从x站开出时车上的人数。无解则输出No answer.
样例输入
5 7 32 4
样例输出
13

思路

定义三个数组,sum数组表示车上的总人数,up数组表示上车人数,down数组表示下车人数。第1站上车人数为a,即up[1]=a,此时总人数sum[1]=a;for循环枚举第2站上下车人数i,即up[2]=i,down[2]=i,第j=2到第j=n-1站,总人数sum[j]=sum[j-1]+up[j]-down[j],下一站上车人数up[j+1]=up[j]+up[j-1],下一站下车人数down[j+1]=up[j]。如果m=sum[n-1],找到i的值,退出;否则,更新i,继续循环。如果没有找到合适的i满足题意,则输出“No answer.”,如果找到正确的i值,则输出x站开出时,车上人数sum[x]。

代码

#include <bits/stdc++.h>
using namespace std;
int sum[50],up[25],down[25];

int main(){
	int a,n,m,x,i,j;
	cin>>a>>n>>m>>x;
	up[1]=a;
	sum[1]=a;
	for(i=0;i<=20;++i){
		up[2]=i;
		down[2]=i;
		for(j=2;j<=n-1;++j){
			sum[j]=sum[j-1]+up[j]-down[j];
			up[j+1]=up[j]+up[j-1];
			down[j+1]=up[j];
		}
		if(m==sum[n-1])
			break;
	}
	if(i>20)	cout<<"No answer.";
	else cout<<sum[x];
	return 0;
}

蜜蜂路线

题目描述

一只蜜蜂在下图所示的数字蜂房上爬动,已知它只能从标号小的蜂房爬到标号大的相邻蜂房,现在问你:蜜蜂从蜂房M开始爬到蜂房N,M<N,有多少种爬行路线?
在这里插入图片描述
输入
输入M,N的值。 (N M为不大于1000的正整数)
输出
输出爬行有多少种路线。
样例输入
1 14
样例输出
377

思路

类似于斐波那契数列,处理的数据范围太大,需要用到高精度算法。定义二维数组a【计数】【存放数据】用来计数,二维数组的初始化第一个元素和第二个元素a[0][0]=1,a[1][0]=1,从m爬到n,一共递推n-m次,利用高精度加法,最后去除前导0,输出第n-m行的所有值即可。

代码

#include <bits/stdc++.h>
using namespace std;
int a[1005][1005];

int main(){
	int m,n,i,k,j,x;
	cin>>m>>n;
	a[0][0]=1;									//初始化 
	a[1][0]=1;
	k=0;
	for(i=2;i<=n-m;++i){						//递推 
		for(j=0;j<1000;++j){					//高精度加法 
			x=a[i-1][j]+a[i-2][j]+k;
			a[i][j]=x%10;
			k=x/10;
		}
	}
	for(i=999;i>=0;--i)							//去除前导0 
		if(a[n-m][i]>0)
			break;
	for(j=i;j>=0;--j)
		cout<<a[n-m][j];
	return 0;
}

极值问题

题目描述

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

思路

化简:(n * n-n * m-m * m)^2=(m * m+n * m-n * n) ^2=(m * m+n * m-n * n) ^2=(m+n) ^2-m * n-2 * n * n=(m+n) ^2-(m+n) * n-n * n=1;即n=(m+n),m=n。初始化m=n=1,因为题目要求n<=k,故循环条件为更新后的n值m+n<=k,循环中不断更新n与m的值,当循环条件不满足时,即m,n最大。

代码

#include <bits/stdc++.h> 
using namespace std;


int main(){
	int k,m,n,t;
	cin>>k;
	m=1;
	n=1;
	while(m+n<=k){
		t=m+n;
		m=n;
		n=t;
	}
	cout<<"m="<<m<<endl;
	cout<<"n="<<n<<endl;
	return 0;
}

邮票问题

题目描述

设有已知面额的邮票m种,每种有n张,用总数不超过n张的邮票,能从面额1开始,最多连续组成多少面额。(1≤m≤100,1≤n≤100,1≤邮票面额≤255)
输入
第一行:m,n的值,中间用一空格隔开。
第二行:A[1…m](面额),每个数中间用一空格隔开。
输出
连续面额数的最大值
样例输入
3 4
1 2 4
样例输出
14

思路

从面额1开始,要连续组成面额,必须邮票的面额里有1,所以如果不存在面额为1的邮票,直接输出“0”即可;将所有邮票的面额存入数组a,初始化每张邮票张数为1,对于任意一面额n,张数=a[i]的张数+n-a[i]的张数(要求n必须大于a[i],防止下标越界),不断计算更新组成n的最小张数,如果面额n没有组成它的张数或者组成它的最小张数大于n,退出循环。

代码

#include <bits/stdc++.h>
using namespace std;
int a[105],z[100005];

int main(){
	int m,n,i,j,x,max;
	cin>>m>>n;
	for(i=1;i<=m;++i){
		cin>>a[i];
		z[a[i]]=1;
	}
	sort(a+1,a+m+1);
	if(a[1]!=1)
		cout<<"0";
	else{
		max=1;
		while(1){
			for(i=1;i<=m;++i){
				if(max>a[i])
					x=z[a[i]]+z[max-a[i]];
				if(z[max]==0||x<z[max])
					z[max]=x;
			}
			if(z[max]>n||z[max]==0)
				break;
			max++;
		}
		cout<<max-1;
	}
	return 0;
}

集合的划分

题目描述

设S是一个具有n个元素的集合,S={a1,a2,……,an},现将S划分成k个满足下列条件的子集合S1,S2,……,Sk ,且满足:
在这里插入图片描述
则称S1,S2,……,Sk是集合S的一个划分。它相当于把S集合中的n个元素a1 ,a2,……,an 放入k个(0<k≤n<30)无标号的盒子中,使得没有一个盒子为空。请你确定n个元素a1 ,a2 ,……,an 放入k个无标号盒子中去的划分数S(n,k)对1e9+7取余之后的值。

输入
输入n,k
输出
输出S(n,k) mod 1e9+7 的值
样例输入
23 7
样例输出
968438818

思路

要开long long,s(n,k)=s(n-1,k-1)+k*s(n-1,k),递归即可

代码

#include <bits/stdc++.h>
using namespace std;
const long long M=1e9+7;
long long s(int n,int k){
	if(k==n||k==1)	return 1;
	else return (s(n-1,k-1)+k*s(n-1,k))%M;
}



int main(){
	int n,k;
	cin>>n>>k;
	cout<<s(n,k);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值