寒假笔记·递归过程

对过程的直接递归

建立动态数组,对每一状态求出递归情况。

例题:P2719 搞笑世界杯

原题地址
题目描述

随着世界杯小组赛的结束,法国,阿根廷等世界强队都纷纷被淘汰,让人心痛不已. 于是有人组织了一场搞笑世界杯,将这些被淘汰的强队重新组织起来和世界杯一同比赛.你和你的朋友欣然去购买球票.不过搞笑世界杯的球票出售方式也很特别,它们只准备了两种球票.A 类票------免费球票 B 类票-------双倍价钱球票.购买时由工作人员通过掷硬币决定,投到正面,的买A类票, 反面的买B类票.并且由于是市场经济,主办方不可能倒贴钱,所以他们总是准备了同样多的A类票和B类票.你和你的朋友十分幸运的排到了某场精彩比赛的最后两个位置.

这时工作人员开始通过硬币售票.不过更为幸运的是当工作人员到你们面前时他发现已无需再掷硬币了,因为剩下的这两张票全是免费票。

你和你的朋友在欣喜之余,想计算一下排在队尾的两个人同时拿到一种票的概率是多少(包括同时拿A 类票或B类票) 假设工作人员准备了2n 张球票,其中n 张A类票,n 张B类票,并且排在队伍中的人每人必须且只能买一张球票(不管掷到的是该买A 还是该买B).

输入输出格式

输入格式:
输入文件仅一行,包含球票数2n . 其中,0<n<=1250 ,n 为整数。

输出格式:
输出文件只包含一个数,为拿到同一种票的概率,精确到小数点后4 位。

输入输出样例

输入样例#1:
256
输出样例#1:
0.9500
代码:
首先开二维数组 dp [ i ] [ j ] . 代表已售 i 张 A , j 张 B 时后两人买到的票相同的概率。

很显然,dp [ 1 ] [ 0 ] 与 dp [ 0 ] [ 1 ] 的初始值应该为 1 ,因为当只有一张票被售出时,我们可以默认后两个人买的票类型相同,所以买到票相同的概率位 100% .

扫描二维码关注公众号,回复: 5540895 查看本文章

然后开始推状态转移方程:dp [ i ] [ j ] = ( dp [ i - 1 ] [ j ] + dp [ i ] [ j - 1 ] ) * 0.5 。

每一个状态 dp [ i ] [ j ] 都是由 dp [ i - 1 ] [ j ] 和 dp [ i ] [ j - 1 ] 得到的,因为每次卖票要么会卖 A ,要么会卖 B ,根据加法计数原理加起来就好了,但是由于两种情况是由抛硬币来决定的,所以发生的几率都会 50% 。

于是乎,dp [ i ] [ j ] = ( dp [ i - 1 ] [ j ] + dp [ i ] [ j - 1 ] ) * 0.5 。

代码就简单了,注意开始的初始化:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<iostream>
#include<cmath>
using namespace std;
double qq[1300][1300];//存储概率的动态数组
int main()
{
    int n,i,j;
    scanf("%d",&n);
    n/=2;
    memset(qq,0,sizeof(qq));
    for(i=2;i<=n;i++)
        qq[i][0]=qq[0][i]=1.0;
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
        {
            qq[i][j]=0.5*qq[i-1][j]+0.5*qq[i][j-1];
        }
    printf("%.4lf\n",qq[n][n]);
    return 0;
}

例题:P2690 接苹果

原题地址
题目描述

很少有人知道奶牛爱吃苹果。农夫约翰的农场上有两棵苹果树(编号为1和2), 每一棵树上都长满了苹果。奶牛贝茜无法摘下树上的苹果,所以她只能等待苹果 从树上落下。但是,由于苹果掉到地上会摔烂,贝茜必须在半空中接住苹果(没有人爱吃摔烂的苹果)。贝茜吃东西很快,她接到苹果后仅用几秒钟就能吃完。每一分钟,两棵苹果树其中的一棵会掉落一个苹果。贝茜已经过了足够的训练, 只要站在树下就一定能接住这棵树上掉落的苹果。同时,贝茜能够在两棵树之间 快速移动(移动时间远少于1分钟),因此当苹果掉落时,她必定站在两棵树其中的一棵下面。此外,奶牛不愿意不停地往返于两棵树之间,因此会错过一些苹果。苹果每分钟掉落一个,共T(1<=T<=1000)分钟,贝茜最多愿意移动W(1<=W<=30) 次。现给出每分钟掉落苹果的树的编号,要求判定贝茜能够接住的最多苹果数。 开始时贝茜在1号树下。

输入输出格式

输入格式:
第一行2个数,T和W。接下来的t行,每行一个数,代表在时刻t苹果是从1号苹果树还是从2号苹果树上掉下来的。

输出格式:
对于每个测试点,输出一行,一个数,为奶牛最多接到的苹果的数量。

输入输出样例

输入样例#1:
7 2
2
1
1
2
2
1
1
输出样例#1:
6
代码:
dp[i][j]表示奶牛在第i分钟内转移了j次能够接到的最多苹果

那么显而易见,对于每一分钟来说,枚举转移次数从而得到解

dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])同时如果 a[i]==j%2+1 即奶牛在这一分钟可以见到苹果 那么把dp[i][j]++

初始化是数组一开始都是0

最终的答案是到第T分钟时奶牛走1~w步能够取得的最多苹果

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
using namespace std;
int qq[1010][35];
int t,w,a[1010],ans;
int main()
{
    int i,j;
    scanf("%d%d",&t,&w);
    for(i=1;i<=t;i++)
        scanf("%d",&a[i]);
    memset(qq,0,sizeof(qq));
    for(i=1;i<=t;i++)
        for(j=0;j<=w&&j<=t;j++)
        {
            if(j==0) qq[i][j]=qq[i-1][j];
            qq[i][j]=max(qq[i-1][j],qq[i-1][j-1]);
            if(j%2+1==a[i]) qq[i][j]++;
        }
    ans=qq[t][0];
    for(i=1;i<=w;i++)
    {
        if(qq[t][i]>ans) ans=qq[t][i];
    }
    printf("%d\n",ans);
    return 0;
}

递归求路径数

例题:P1002 过河卒

原题地址
题目描述

棋盘上AA点有一个过河卒,需要走到目标BB点。卒行走的规则:可以向下、或者向右。同时在棋盘上CC点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。

棋盘用坐标表示,AA点(0, 0)(0,0)、BB点(n, m)(n,m)(nn, mm为不超过2020的整数),同样马的位置坐标是需要给出的。

现在要求你计算出卒从AA点能够到达BB点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。

输入输出格式

输入格式:
一行四个数据,分别表示BB点坐标和马的坐标。

输出格式:
一个数据,表示所有的路径条数。

输入输出样例

输入样例#1:
6 6 3 3
输出样例#1:
6
说明

结果可能很大!
代码:
只要列出关系式f[i][k]=f[i-1][k]+f[i][k-1](表示从(0,0)移动到(i,k)点一共可能的路径数目)基本就解决了,然后要注意i、k为0再-1就会越界,还有要将那几个马控制的点的f[i][k]置为零。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<string>
#include<iostream>
using namespace std;
long long int qq[25][25],vv[25][25];
int main()
{
    int i,j,m,n,a,b;
    scanf("%d%d%d%d",&m,&n,&a,&b);
    memset(qq,0,sizeof(qq));
    memset(vv,0,sizeof(vv));
    qq[2][2]=1;
    for(i=2;i<=m+2;i++)
        for(j=2;j<=n+2;j++)
            vv[i][j]=1;
    vv[a+2][b+2]=0;
    vv[a+2+2][b+1+2]=0;
    vv[a+2+2][b-1+2]=0;
    vv[a+1+2][b+2+2]=0;
    vv[a+1+2][b-2+2]=0;
    vv[a-1+2][b-2+2]=0;
    vv[a-1+2][b+2+2]=0;
    vv[a-2+2][b-1+2]=0;
    vv[a-2+2][b+1+2]=0;
    for(i=2;i<=m+2;i++)
        for(j=2;j<=n+2;j++)
        {
            if(i==2&&j==2) continue;
            if(vv[i][j])
            {
                qq[i][j]=qq[i-1][j]+qq[i][j-1];
            }
        }
    printf("%lld\n",qq[m+2][n+2]);
    return 0;
}

多重递归

例题:P1990 覆盖墙壁

原题地址

题目描述

你有一个长为N宽为2的墙壁,给你两种砖头:一个长2宽1,另一个是L型覆盖3个单元的砖头。如下图:

0 0
0 00
砖头可以旋转,两种砖头可以无限制提供。你的任务是计算用这两种来覆盖N2的墙壁的覆盖方法。例如一个23的墙可以有5种覆盖方法,如下:

012 002 011 001 011
012 112 022 011 001
注意可以使用两种砖头混合起来覆盖,如2*4的墙可以这样覆盖:

0112
0012
给定N,要求计算2N的墙壁的覆盖方法。由于结果很大,所以只要求输出最后4位。例如213的覆盖方法为13465,只需输出3465即可。如果答案少于4位,就直接输出就可以,不用加0,如N=3,时输出5。

输入输出格式

输入格式:
一个整数N(1<=N<=1000000),表示墙壁的长。

输出格式:
输出覆盖方法的最后4位,如果不足4位就输出整个答案。

输入输出样例

输入样例#1:
13
输出样例#1:
3465

代码:
首先,既然是递推,那么分好状态就是一件非常重要的事情。这里本人直接讲状态。

(下文中的F[N]表示铺满前N*2的面积的墙的方案数;“一列”指长为1,宽为2的墙壁)

1.当这面墙的最后一列被铺满时(如下图所示)

在这里插入图片描述

以这种状态结尾的方案数为F[N-1]。

2.当这面墙的最后两列被铺满时(如下图所示,注意颜色的区别)

在这里插入图片描述

以这种状态结尾的方案数为F[N-2]。

大家也看到,前两种状态很容易想到,也很容易描述。

但是,L形的瓷砖又怎么办呢?
我们可以用一个数组G[N]来表示铺满前(N+1)*2的面积的墙,但是第(N+1)列有一个瓷砖已经被铺过(注意,是已经被铺过!)的方案数。

所以,L形瓷砖的问题就已经被“初步”解决了。

所以,下面这种情况的方案数就是G[N-2](因为实际上第N列已经铺满了,所以这里要处理的是前N-1列墙,所以多减了1)(如下图所示):

在这里插入图片描述

同理,这一种情况的方案数也是G[N-2]:

在这里插入图片描述

OK,现在问题来了:这个G数组应该怎么维护呢?

不急,我们可以画图。

首先,第一种情况就是直接先让它变成一个长方形:

在这里插入图片描述

以这种状态结尾的方案数为F[N-3]。

第二种情况是,加上一块砖后,它仍然不是一个长方形:

在这里插入图片描述

so,这第二种情况的方案数就是G[N-3](可能需要转一下弯,希望大家能弄懂)。

所以,G[N-2](注意,不是G[N])的方案数就等于F[N-3]+G[N-3]。

稍微化简一下,就可以得出:G[N]=F[N-1]+G[N-1]。

所以,F[N]的转移方程就是:

F[N]=F[N-1]+F[N-2]+2*G[N-2](别忘了前面讲过G[N-2]的情况有两种)

而G[N]的转移方程就是:G[N]=F[N-1]+G[N-1]。

初始化:F[0]=1,G[0]=0;F[1]=G[1]=1;

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<string>
using namespace std;
const int maxn=1000002;
const int mod=10000;

int f[maxn],g[maxn];

int main()
{
    int n;

    scanf("%d",&n);

    f[0]=1;
    g[0]=0;
    f[1]=g[1]=1;

    for(int i=2;i<=n;i++)
    {
        f[i]=((f[i-1]+f[i-2])%mod+2*g[i-2]%mod)%mod;

        g[i]=(g[i-1]+f[i-1])%mod;
    }

    printf("%d\n",f[n]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43918350/article/details/87967745