递归与递推总结

汉诺塔问题

思路
移动过程只有三步
1.A移动N-1个盘到B: F(n-1);
2.A移动最大盘到C: F(1)即为1;
3.B移动N-1个盘到C: F(n-1);
移动次数的公式F(n)=F(n-1)*2+1

#include <iostream>
#include <cstdio>
using namespace std;
void slove(int n,char a,char b,char c)///有n个塔层由a经过b的中转运送到c
{
    if(n==1)///只有一个的时候,直接将a上的放到c上
    {
        printf("%c-%d-%c\n",a,1,c);
        return;
    }
    else///将n-1层塔由a到b,当最后一个(第n个)运到c时,再由b运到c
    {
        slove(n-1,a,c,b);///有n-1个塔层由a经过c的中转运送到b
        printf("%c-%d-%c\n",a,n,c);///把a中的1层塔移动到c
        slove(n-1,b,a,c);///有n-1个塔层由b经过a的中转运送到c
    }
}
int main()
{
    int n;
    cin>>n;
    slove(n,'A','B','C');
    return 0;
}


问题 P: 【递归与递推】Hanoi双塔问题

题目
给定A,B,C三根足够长的细柱,在A柱上放有2n个中间有空的圆盘,共有n个不同的尺寸,每个尺寸都有两个相同的圆盘,注意这两个圆盘是不加区分的(下图为n=3的情形)。现要将 这些国盘移到C柱上,在移动过程中可放在B柱上暂存。要求:
(1)每次只能移动一个圆盘;
(2) A、B、C三根细柱上的圆盘都要保持上小下大的顺序;
任务:设An为2n个圆盘完成上述任务所需的最少移动次数,对于输入的n,输出An。
输入
输入一个正整数n,表示在A柱上放有2n个圆盘。
输出
输出一个正整数,为完成上述任务所需的最少移动次数An。
思路
F(n)=F(n-1)*2+2

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
string ans[205];
string add(string a,string b)///高精度加法
{
    string c;
    reverse(a.begin(),a.end());
    reverse(b.begin(),b.end());
    int carry=0,temp;
    for(int i=0;i<a.size()||i<b.size();i++)
    {
        temp=carry;
        if(i<a.size()) temp+=a[i]-'0';
        if(i<b.size()) temp+=b[i]-'0';
        c.push_back(char(temp%10+'0'));
        carry=temp/10;
    }
    if(carry!=0) c.push_back(char('0'+carry));
    reverse(c.begin(),c.end());
    int i;
    for(i=0;c[i]=='0'&&i<c.size()-1;i++);///消除前导0,但保证至少有一个
    c=c.substr(i,c.size()-i);
    return c;
}
string multiply(string a,string b)
{
    reverse(a.begin(),a.end());
    reverse(b.begin(),b.end());
    int c[10005]={0};
    string ans;
    for(int i=0;i<a.size();i++)///注意下标都是从0开始
    {
        for(int j=0;j<b.size();j++)
        {///i+j为两个数相乘完的那个数所在位置,例如一个数的第二位×另一个数的第二位,所得数在结果的第四位
            c[i+j]=c[i+j]+(a[i]-'0')*(b[j]-'0');///算法核心,不进位乘法
        }
    }
    int temp,carry=0;///该位数的和temp,进位carry,与加法类似
    for(int i=0;i<a.size()+b.size();i++)
    {
        temp=c[i]+carry;
        c[i]=temp%10;
        carry=temp/10;
    }
    for(int i=0;i<a.size()+b.size();i++) ans.push_back(char('0'+c[i]));
    int i;
    for(i=ans.size()-1;ans[i]=='0'&&i>=1;i--); ans=ans.substr(0,i+1);
    if(carry!=0) ans.push_back(char('0'+carry));
    reverse(ans.begin(),ans.end());
    return ans;
}
void getans()
{
    ans[1]="2";
    string two("2");
    for(int i=2;i<=200;i++)
    {
        ans[i]=add(multiply(ans[i-1],two),two);
    }
}
int main()
{
    getans();
    int n;cin>>n;
    cout<<ans[n]<<endl;
    return 0;
}


问题 D: 【递归与递推】2的幂次方

题目
任何一个正整数都可以用2的幂次方表示。例如:
137=27+23+20
同时约定方次用括号来表示,即ab 可表示为a(b)。
由此可知,137可表示为:
2(7)+2(3)+2(0)
进一步:7=22+2+20 (21用2表示)
3=2+20
所以最后137可表示为:
2(2(2)+2+2(0))+2(2+2(0))+2(0)
又如:
1315=210 +28 +25 +2+1
所以1315最后可表示为:
2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)
输入
一行,一个正整数(n≤20000)
输出
符合约定的n的0,2表示(在表示中不能有空格)
思路
参考算法:递归输出某十进制数的二进制表示的算法。(主要是在递归回溯的时候才输出,而非计算出来就输出。)

#include <stdio.h>
void fun(int n)
{
    int t;
    if(n==0)
        return;
    else
    {
        t=n%2;
        fun(n/2);
        printf("%d",t);
    }
}
int main()
{
    fun(5);
    return 0;
}

本着从易到难的原则,可以考虑先实现将137表示为:2(7)+2(3)+2(0)的程序。
关于加号的输出:可以考虑判断当前项是否二进制序列的最高位(x递归到下一层slove(x/2,bit+1)的数x/2是0,即返回了)。是最高位则当前项左侧不输出“+”,否则在当前项左侧输出“+”.
关于把指数也转变为0和2的序列:在输出每一项时判断指数是否超过1,超过则先输出“2(”,然后把该指数与0传入递归函数,递归显示该指数的表示。然后在输出后半边括号。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void slove(int x,int bit)
{
    if(x==0) return ;
    else
    {
        slove(x>>1,bit+1);
        if(x%2)///回溯的时候才输出
        {
            if(bit==1)
            {
                if(x!=1) cout<<"+2";
                else  cout<<2;
            }
            else
            {
                ///x不为二进制的最高位时,即x不为1时,前有加号
                ///例如x=1000101(2),x每次移动一位,当他移动到只剩下最高位的1时,x=1,前面没有'+'
                if(x!=1) cout<<'+';
                if(bit==0) cout<<"2(0)";
                else
                {
                    cout<<"2(";
                    slove(bit,0);///2^bit,bit>1,继续递归
                    cout<<")";
                }
            }
        }
    }
}
int main()
{
    int n;cin>>n;
    slove(n,0);
    return 0;
}


问题 E: 【递归与递推】攀天梯

题目:
北武当山主峰四周几乎都是陡壁悬崖,只有一条人造“天梯”.可攀,天梯由n级就山凿筑的石阶组成,欢欢打算通过天梯攀上北武当山主峰。攀天梯时,他有时一步一级石阶,有时一步两级,那么,他攀上这n级的天梯有多少种不同的方法?
思路
爬n级台阶的方法可以由爬n-1级(再爬1级)和爬n-2级(再爬2级)得到
代码

#include <iostream>
 
using namespace std;
long long  ans[100];
void getans()
{
    ans[1]=1,ans[2]=2;
    for(int i=3;i<=80;i++)
    {
        ans[i]=ans[i-1]+ans[i-2];
    }
}
int main()
{
    getans();
    int n;cin>>n;
    cout << ans[n] << endl;
    return 0;
}

问题 J: 【递归与递推】数的计数
题目
我们要求找出具有下列性质数的个数(包含输入的自然数n),先输入一个自然数n(n≤1000),然后对此自然数按照如下方法进行处理:
(1)不作任何处理;
(2)在它的左边加上一个自然数,但该自然数不能超过原数的一半;
(3)加上数后,继续按此规则进行处理,直到不能再加自然数为止。
输入
一个正整数n。
输出
符合以上性质的数的个数。
提示
样例说明:满足条件的数为6,16,26,36,126,136。
思路
n的答案个数由1到n/2得到,ans[1]=1,ans[2]=2;
代码

#include <iostream>
#include <cmath>
using namespace std;
long long ans[1005];
void slove()
{
    ans[1]=1;
    for(int i=2;i<=1000;i++)
    {
        for(int j=1;j<=i/2;j++)
        {
            ans[i]+=ans[j];
        }
        ans[i]++;
    }
}
int main()
{
    int n;
    cin>>n;
    slove();
    cout<<ans[n]<<endl;
    return 0;
}


问题 F: 【递归与递推】递归函数(reduce)

题目
考虑如下的3参数递归函数w(a,b,c);
如果a≤0或b≤0或c≤0,则w(a,b,c)=1;
如果a>20或b>20或c>20,则w(a,b,c)=w(20,20,20);
如果a其他情况下:w(a,b,c)=w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1, b-1,c-1)。
设计一个程序,快速计算w(a,b,c)并给出结果。
输入
1行整数,包含3个数值,分别对应a、b和c的值。
输出
一个数,即w(a,b,c)的结果。
思路
直接代式子进去即可

#include <iostream>

using namespace std;
int ans[25][25][25]={0};
void getans()
{
    for(int i=0;i<=20;i++)
        for(int j=0;j<=20;j++)
            for(int k=0;k<=20;k++)
                if(i==0||k==0||j==0) ans[i][j][k]=1;
    for(int i=1;i<=20;i++)///从1开始
        for(int j=1;j<=20;j++)
            for(int k=1;k<=20;k++)
                ans[i][j][k]=ans[i-1][j][k]+ans[i-1][j][k-1]+ans[i-1][j-1][k]-ans[i-1][j-1][k-1];
}
int main()
{
    getans();
    int a,b,c;
    cin>>a>>b>>c;
    if(a<=0||b<=0||c<=0) cout<<1<<endl;///忘了考虑小于0的情况了
    else if(a>20||b>20||c>20) cout<<ans[20][20][20]<<endl;
    else cout<<ans[a][b][c]<<endl;
    return 0;
}


问题 G: 【递归与递推】滑雪

题目
Michael喜欢滑雪。这并不奇怪,因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道在一个区域中最长的滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。
下面是一个例子:
一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可行的滑坡为24-17-16-1(从24开始,在1结束)。当然25-24-23-…-3-2-1更长。事实上,这是最长的一条。
输入
第1行为表示区域的二维数组的行数R和列数C(1≤R,C≤l00)。下面是R行,每行有C个数,代表高度。
输出
输出区域中最长滑坡的长度
思路
记忆化搜索,dp[x][y]=max(dp[x][y],dp[nx][ny]+1); 将dp[nx][ny]换成slove(nx,ny)即可,但是在循环结束时要记得返回dp[x][y];
遍历一遍dp[i][j]求出最大值
注意
求的是最长距离不是高度

#include <iostream>

using namespace std;
int a[105][105];
int dp[105][105];
int X[4]={1,-1,0,0};
int Y[4]={0,0,-1,1};
int n,m,maxn=1;
inline bool check(int x,int y){if(x<1||x>n||y<1||y>m) return false; return true;}
int slove(int x,int y)///记忆化搜索,是长度不是高度
{
    if(dp[x][y]) return dp[x][y];///已为最大值直接返回
    int flag=1;
    for(int i=0;i<4;i++)
    {
        int nx=x+X[i],ny=y+Y[i];///获得四个坐标
        if(check(nx,ny)&&a[nx][ny]<a[x][y])///符合条件,可以往下递归
        {
            flag=0;///可以往周围滑雪的标记
            dp[x][y]=max(dp[x][y],slove(nx,ny)+1);///dp[x][y]=max(dp[x][y],dp[nx][ny]+1);
        }
    }
    if(flag) dp[x][y]=1;///递归边界,已经不能再往周围滑雪,说明dp[x][y]=1,长度只为1
    return dp[x][y];///记得返回最大值,否则没有返回值会wa
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>a[i][j];
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) slove(i,j),maxn=max(maxn,dp[i][j]);
    cout<<maxn<<endl;
    return 0;
}


问题 H: 【回溯法】马拦过河卒

题目
棋盘上A点有一个过河卒,需要走到目标B点。卒行走的规则:可以向下、或者向右。同时在棋盘上C点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
棋盘用坐标表示,A点(0, 0)、B点(n, m)(n, m为不超过20的整数),同样马的位置坐标是需要给出的。现在要求你计算出卒从A点能够到达B点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
输入
一行四个数据,分别表示B点坐标和马的坐标。(保证所有的数据有解)
输出
一个数据,表示所有的路径条数。
思路
dp[x][y]=dp[x-1][y]+dp[x][y-1];(具体写法见代码)
注意
不仅结果要开long long,返回值也要是long long,马所在的地方也要设置为不能走

#include <iostream>
#include <cstring>
#include <fstream>
using namespace std;
int n,m;
long long dp[25][25];
int X[8]={1,1,-1,-1,2,-2,-2,2};
int Y[8]={2,-2,2,-2,1,-1,1,-1};
int nx[8],ny[8];
int x2,y2;
bool check(int x,int y)
{
    if(x>n||x<0||y>m||y<0||(x==x2&&y==y2)) return false;///马所在的地方也走不了
    for(int i=0;i<8;i++) if(nx[i]==x&&ny[i]==y) return false;
    return true;
}
long long slove(int x,int y)///返回值也要是long long才行!!!
{
    if(dp[x][y]) return dp[x][y];///已有值,返回,记忆化搜索
    if(check(x-1,y)) dp[x][y]+=slove(x-1,y);///递归
    if(check(x,y-1)) dp[x][y]+=slove(x,y-1);///递归
    return dp[x][y];///最后要返回dp[x][y]
}
int main()
{
    //freopen("a.txt","r",stdin);
    //freopen("b.txt","w",stdout);
    //memset(dp,0,sizeof(dp));
    cin>>n>>m>>x2>>y2;
    for(int i=0; i<8; i++)   nx[i]=x2+X[i],ny[i]=y2+Y[i];///获得马移动后的坐标
    dp[0][0]=1;///初始化dp[0][0]=1;
    slove(n,m);
    if(!check(n,m))  dp[n][m]=0;///其实没必要,为了保险
    cout <<dp[n][m] << endl;
    return 0;
}
/*
20 20 6 7
20690864515
15 17 6 7
58967328
5 5 3 4
0
20 20 105 105
407575348
*/

问题 I: 【递归与递推】乐乐的棋盘

题目:
乐乐有一个棋盘,共有m行n列,一只棋子从左上角开始,向右下角移动,每次只能向下或向右移动一次。然而这个棋盘中有一些障碍物,这些障碍物使得这个棋子不能进入这些格子,问这个棋子从左上角到达右下角共有多少种不同的移法?
如果到达不了,则输出0。
输入
第1行:两个整数m,n (0<m,n≤100);
后面有m行,每行有n个数(0或1),如果是1,则表示这个方格中有障碍物。
输出
求得的方案数。
思路
与上一题滑雪的类似,只是n,m的范围较大(30以上就需要了),所以要使用高精度

#include <iostream>
#include <string>
#include <algorithm>
#include <cstring>
using namespace std;
int n,m;
int X[4]={0,-1};
int Y[4]={-1,0};
int a[105][105];
string dp[105][105];
string add(string a,string b)///高精度加法
{
    string c;
    reverse(a.begin(),a.end());
    reverse(b.begin(),b.end());
    int carry=0,temp;
    for(int i=0;i<a.size()||i<b.size();i++)
    {
        temp=carry;
        if(i<a.size()) temp+=a[i]-'0';
        if(i<b.size()) temp+=b[i]-'0';
        c.push_back(char(temp%10+'0'));
        carry=temp/10;
    }
    if(carry!=0) c.push_back(char('0'+carry));
    reverse(c.begin(),c.end());
    int i;
    for(i=0;c[i]=='0'&&i<c.size()-1;i++);///消除前导0,但保证至少有一个
    c=c.substr(i,c.size()-i);
    return c;
}

void ini(int n,int m)
{
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) dp[i][j]="0";
    dp[1][1].push_back('1');///一开始放错位置了,一直是0
}

string slove(int x,int y)
{
    if(dp[x][y]!="0") return dp[x][y];
    for(int i=0;i<2;i++)
    {
        int nx=x+X[i],ny=y+Y[i];
        if(nx<1||nx>n||ny>m||ny<1||a[nx][ny]) continue;
        dp[x][y]=add(dp[x][y],slove(nx,ny));
    }
    return dp[x][y];
}
int main()
{
    while(cin>>n>>m)
    {
        memset(a,0,sizeof(a));
        for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>a[i][j];
        ini(n,m);
        slove(n,m);
        cout<<dp[n][m]<<endl;
    }
    return 0;
}

问题 K: 【递归与递推】骑士游历问题

题目
设有一个m*n的棋盘(2≤m≤50,2≤n≤50),在棋盘上任一点有一个中国象棋“马”,马走的规则为:马走日字;马只能向右走。当m,n给出后,同时给出马起始的位置和终点的位置,试找出从起点到终点所有路径的数目。
输入
m,n,xl,yl,x2,y2(分别表示棋盘大小、起点坐标和终点坐标)。
输出
路径数目(若不存在,则输出0)。
思路
同样的,思路和上一题很类似,使用记忆话搜索,但是存在一个问题:地图上从起点开始有很多点达不到,从终点回溯到起点的过程,可能会进过这些点,然后就会重复搜索,所以我们需要在一个点(x,y)搜索完了,若其dp[x][y]仍旧=0,将其置为-1;这样可以防止重复搜索这个点,或者使用visit[ ][ ]数组也行
注意
我一开始没考虑到一个点达不到,然后重复搜索的情况,T了

#include <iostream>
using namespace std;
int n,m,sx,sy,ex,ey;
long long dp[55][55]={0};
int Y[4]={1,-1,2,-2};
int X[4]={-2,-2,-1,-1};
long long slove(int x,int y)
{
    if(dp[x][y]) return dp[x][y];
    for(int i=0;i<4;i++)
    {
        int nx=x+X[i],ny=y+Y[i];
        if(nx<1||ny<1||nx>n||ny>m) continue;
        long long temp=slove(nx,ny);
        if(temp!=-1) dp[x][y]+=temp;
    }
    if(dp[x][y]==0) dp[x][y]=-1;///起点无法到达,置为-1,防止重复搜索
    return dp[x][y];
}
int main()
{

    cin>>n>>m>>sx>>sy>>ex>>ey;
    dp[sx][sy]=1;
    slove(ex,ey);
    cout << dp[ex][ey] << endl;
    return 0;
}
/*
50 50 2 2 50 50
*/


问题 O: 【递推与递归】棋子移动

题目 :
有2n(n≥4)个棋子排成一行,开始位置为白子全部在左边,黑子全部在右边。例如n=5时:00000*****。移动棋子的规则是:每次必须同时移动相邻两个棋子,颜色不限,可以左移也可以右移到空位上去,但不能调换两个棋子的左右位置。每次移动必须跳过若干个棋子(不能平移),要求最后能移成黑白相间的一行棋子。如n=5时,成为:__00000*。

输入
仅输入黑棋和白棋的数目n(4≤n≤1000)。
输出
输出各行每一步的移动结果,最后一行输出总移动步数,其中用“O”(大写字母)表示白棋,用“*”表示黑棋。“__”(两个下划线)表示两个空位。
思路
1)前面n=4的时候的移动规律直接打表
2)n>4时通过:

		swap(temp[n],temp[2*n+1]);
        swap(temp[n-1],temp[2*n]);
        cout<<temp<<endl;
        swap(temp[n],temp[2*n-1]);
        swap(temp[n-1],temp[2*n-2]);
        cout<<temp<<endl;

转化为n-1的情况,一直到n=4跳回1)
代码

#include <iostream>
#include <cstring>
using namespace std;
string ans[5]={"OOO__***O*","OOO*O**__*","O__*O**OO*","O*O*O*__O*","__O*O*O*O*"};
string temp;
int main()
{
    int n;cin>>n;
    int k=n;
    for(int i=0;i<n;i++) temp.push_back('O');
    for(int i=0;i<n;i++) temp.push_back('*');
    temp.push_back('_'); temp.push_back('_');
    int step=0;
    k-=4;
    for(int i=1;i<=k;i++)
    {
        swap(temp[n],temp[2*n+1]);
        swap(temp[n-1],temp[2*n]);
        cout<<temp<<endl;
        swap(temp[n],temp[2*n-1]);
        swap(temp[n-1],temp[2*n-2]);
        cout<<temp<<endl;
        step+=2;n--;
    }
    step+=5;
    for(int i=0;i<5;i++)
    {
        cout<<ans[i];
        for(int j=1;j<=k;j++)
        {
            cout<<"O*";
        }
        cout<<endl;
    }
    cout<<"step="<<step;
    return 0;
}


问题 M: 【递归与递推】兔子繁殖

题目
兔子有很强的繁殖能力。1对成年的兔子每个月可以生育一对幼年的兔子,而1对幼年的兔子经过m个月之后,就会长成1对成年的兔子。当一开始有1对成年兔子时,经过d个月以后,共有多少对兔子?你的任务是计算出一对成年兔子经过d个月后,共有多少对兔子,假设整个过程中没有兔子死亡。
输入
输入有多组数据,每组占l行,为两个整数m,d(1≤m≤10,l≤d≤I00),当m=d=0时表示结束。
输出
每行为每组数据对应最后得到的兔子数。
思路
1)通过本题更加了解了斐波那契数列,斐波那契数列是两个月(不是一个月)成熟一只兔子,所以f(n)=f(n-1)+f(n-2);这个月的兔子等于上个月的加上在这个月生出的小兔子数(等于这个月能生兔子的兔子数,也即这个月的前2个月的兔子数)
2)
当n<m时, f(n)=f(n-1)+1(n<m);前m个月每个月多一只兔子
当n>=m时,f(n)=f(n-1)+f(n-m)
注意
1)n=1时,f(n)=2n 不使用高精度会爆 long long
2)使用高进度时前m天,不可以直接使用

for(int i=0;i<m;i++) ans[i].push_back(char(i+1+'0'));

因为当i大于9时就不对了,而应当使用

for(int i=0;i<m;i++) ans[i]=add(ans[i-1],one);

代码

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
string ans[105];
string add(string a,string b)///高精度加法

{
    string c;
    reverse(a.begin(),a.end());
    reverse(b.begin(),b.end());
    int carry=0,temp;
    for(int i=0;i<a.size()||i<b.size();i++)
    {
        temp=carry;
        if(i<a.size()) temp+=a[i]-'0';
        if(i<b.size()) temp+=b[i]-'0';
        c.push_back(char(temp%10+'0'));
        carry=temp/10;
    }
    if(carry!=0) c.push_back(char('0'+carry));
    reverse(c.begin(),c.end());
    int i;
    for(i=0;c[i]=='0'&&i<c.size()-1;i++);///消除前导0,但保证至少有一个
    c=c.substr(i,c.size()-i);
    return c;
}

void getans(int m,int d)
{
    string one="1";
    for(int i=0;i<=d;i++) ans[i].clear();
    ans[0]="1";
    for(int i=0;i<m;i++) ans[i]=add(ans[i-1],one);
    for(int i=m;i<=d;i++)
    {
        ans[i]=add(ans[i-1],ans[i-m]);
    }
}
int main()
{
    int m,d;
    while(cin>>m>>d)
    {
        if(m==0&&d==0) break;
        getans(m,d);
        cout<<ans[d]<<endl;
    }
    return 0;
}


问题 N: 【递归与递推 】帕斯卡的旅行

题目
在一个n×n个方格的游戏板中,每个方格中有一个非负整数。游戏的目标是从游戏板的左上角沿任何合法路径移动到右下角。任何一个方格内的数字规定了离开本方格的一步必须移动的方格数。如果移动的一步越出了游戏板,则这个方向的移动是禁止的。每一步移动只能是向下或向右的。考虑如下图所示的4×4的板,这里正体字表示出发位置,斜体字表示目的位置。后面显示了从出发位置到目的位置的三条路径,其中隐去了与每条路径无关的数字。
输入
输入含有n+l行,第1行是游戏板的行数n(4≤n≤34),接下来是n个数据行,每行含有n个0~9的数字,中间没有空格。
输出
在1行中输出从左上角到右下角的路径数(注:输出的路径数使用长整型数据类型)。
思路
一开是以为在移动步数内可以先右走在下走,原来是只能下走或只能右走,WA了两发
1)dp[x][y]+=slove(i,j); ( (i,j)可以通过向下或向右移动a[i][j]步移动到(x,y)处)
2)然后就是当a[i][j]=0时要让dp[i][j]=-1;因为(i,j)可从起点到达,但是无法去到其他点,这个点dp值会一直等于0,然后重复搜索,然后超时
注意
见代码

#include <iostream>
#include <cstdio>
using namespace std;
int n;
int a[40][40];
long long dp[40][40];
long long slove(int x,int y)
{
    if(a[x][y]==0) return dp[x][y]=-1;///防止死循环,因为其走不出去
    if(dp[x][y]) return dp[x][y];
    for(int i=1;i<=x;i++)
    {
        if(x-i==a[i][j])
        {
            long long temp=slove(i,y);
            if(temp!=-1) dp[x][y]+=temp;///可以从(i,y)到达(x,y)
        }
    }
    for(int j=1;j<=y;j++)
    {
        if(y-j==a[i][j])
        {
            long long temp=slove(x,j);
            if(temp!=-1) dp[x][y]+=temp;///可以从(x,j)到达(x,y)
        }
    }
    if(dp[x][y]==0) dp[x][y]=-1;///不可从起点到达
    return dp[x][y];
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%1d",&a[i][j]);
    long long ANS=0;
    dp[1][1]=1,a[n][n]=1;///防止a[n][n]=0;
    ANS=slove(n,n);
    if(ANS!=-1)cout << ANS << endl;///注意-1情况,wa了一发
    else cout<<0<<endl;
    return 0;
}
/*
测试样例
34
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
1111111111111111111111111111111111
7219428434016265740
4
2331
1213
1231
3110
3
*/

问题 C: 【递归与递推】九连环

题目描述

九连环是由九个彼此套接的圆环和一根横杆组成,九个环从左到右依次为l~9,每个环有两种状 态:1和0,1表示环在杆上,0表示环不在杆上。初始状态是九个环都在杆上,即:111111111,目标状态是九个环都不在杆上,即:000000000,由初始状态到目标状态的变化规则是:
(1)第一环为无论何时均可自由上下横行;
(2)第二只环只有在第一环为1时,才能自由上下;
(3)想要改变第n(n>2)个环的状态,需要先使第一到第(n-2)环均为下杆,且第n-1个环为上杆,而与第n+l个到第九环状态无关;
(4)每改变一个环,记为一步。
现在九连环由111111111变到000000000,求中间第i步的状态。
输入
仅包含一个整数i。
输出
仅包含中间第i步的状态。如果输入的步数大于实际变换所需的步数,则输出-1。

#include<iostream>
#include<string>
 
using namespace std;
string ans[10000];
int cnt=1;
class Ring
{
public:
  Ring(int Num):RingNum(Num){}
  void StartDown();
  void StartUp();
private:
  int RingNum;
  void DownRing(int n);
  void UpRing(int n);
};
 
void Ring::DownRing(int n)
{
  if(n>2) DownRing(n-2);
  ans[cnt+1]=ans[cnt].replace(n-1,1,"0");
  cnt++;
  //cout<<ans[cnt]<<endl;
  if(n>2) UpRing(n-2);
  if(n>1) DownRing(n-1);
}
 
void Ring::UpRing(int n)
{
  if(n>1) UpRing(n-1);
  if(n>2) DownRing(n-2);
  ans[cnt+1]=ans[cnt].replace(n-1,1,"1");
  cnt++;
  if(n>2) UpRing(n-2);
}
 
void Ring::StartDown()
{
  DownRing(RingNum);
}
 
void Ring::StartUp()
{
  UpRing(RingNum);
}
 
int main()
{
    int n;
    while(cin>>n)
    {
        ans[1]="111111111";
        Ring RingN(9);
        RingN.StartDown();
        RingN.StartUp();
        if(n<=341)  cout<<ans[n]<<endl;
        else  cout<<-1<<endl;
    }
    return 0;
}

发布了95 篇原创文章 · 获赞 7 · 访问量 8453

猜你喜欢

转载自blog.csdn.net/Spidy_harker/article/details/100847914