骨牌铺法
题目描述
有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;
}