【疯狂DP】CCFOJ1185、1186_数的划分一、二 题解——从DFS到DP

题目

1185

描述

把正整数N分解成M个正整数的和,即使M个数相同但顺序不同也认为是不同的方案,要求总方案数。如3=1+2跟3=2+1是两个不同的方案。

输入

第一行包含两个整数N和M。

输出

输出一个数表示方案数。

样例

输入

3 2

输出

2

数据范围

1<=M<=N<=50

1186

描述

把正整数N分解成M个非负整数的和,即使M个数相同但顺序不同也认为是不同的方案,要求总方案数。如3=1+2跟3=2+1是两个不同的方案。

输入

第一行输入两个整数。

输出

输出一个整数表示方案数。

样例

输入

2 3

输出

6

数据范围

1<=M<=N<=30

解法

1185

初看此题,数据范围不大,题较简单,于是,我打出了下面的程序:

#include<cstdio>
#include<cstring>
int dfs(int n,int m)
{
    if(m==1)return 1;
    if(m==n)return 1;
    if(n<1)return 0;
    int s=0;
    for(int i=1;i<=n-m+1;i++)
        s+=dfs(n-i,m-1);
    return s;
}
int main()
{
    int m,n;
    scanf("%d %d",&n,&m);
    printf("%d\n",dfs(n,m));
    return 0;
}

但是你输入下面一组数据试试:

50 20

等了很久,很久很久。。。。。。
终于程序没有给出答案:

P.S.这时,电脑似乎炸了,你什么也做不了,只有等。。。。。。

不信你试试。

我们只有换换思路了。

首先,int肯定是装不下的,所以,我们要使用long long。
其次,普通的dfs是承受不了如此巨大的计算量的,我们要对它升级——记忆化搜索。
最后,我们再将记忆化搜索升级——DP

我们先对dfs进行改造:

#include<cstdio>
long long dfs(int n,int m)
{
    if(m==1)return 1;
    if(m==n)return 1;
    if(n<1)return 0;
    long long s=0;
    for(int i=1;i<=n-m+1;i++)
        s+=dfs(n-i,m-1);
    return s;
}
int main()
{
    int m,n;
    scanf("%d %d",&n,&m);
    printf("%I64d\n",dfs(n,m));
    return 0;
}

dfs改造成功。
现在我们来做记忆化搜索。

不知道大家发现没有,dfs在计算中其实许多计算是重复的,所以,我们就可以使用一个二维数组来记录已经计算的结果,再在搜索时调用,就可以实现每个状态只计算一次。

代码如下:

#include<cstdio>
const long long M=52;
long long dp[M][M];
long long dfs(long long n,long long m)
{
    if(m==1)return dp[n][m]=1;
    if(m==n)return dp[n][m]=1;
    if(n<1)return 0;
    if(dp[n][m])return dp[n][m];
    long long s=0;
    for(int i=1;i<=n-m+1;i++)
        s+=dfs(n-i,m-1);
    return dp[n][m]=s;
}
int main()
{
    int m,n;
    scanf("%d %d",&n,&m);
    printf("%I64d\n",dfs(n,m));
    return 0;
}

最后,我们来做DP。

DP也和记忆化搜索一样,用一个二维数组来记录已经计算的结果,但它与记忆化搜索也有不同。

我们先根据之前两个程序的经验得出状态转移方程,请读者自己总结。

由此,我们就可以得到这段核心代码:

for(int j=1;j<=m;j++)
        for(int i=1;i<=n;i++)
            for(int k=1;k<i;k++)
            {
                if(j==1){dp[i][j]=1;continue;}
                if(i==j){dp[i][j]=1;continue;}
                dp[i][j]+=dp[i-k][j-1];
            }

最后,我们再加上其他部分就成为了一个完整的AC程序。

代码如下:

dfs:

#include<cstdio>
long long dfs(int n,int m)
{
    if(m==1)return 1;
    if(m==n)return 1;
    if(n<1)return 0;
    long long s=0;
    for(int i=1;i<=n-m+1;i++)
        s+=dfs(n-i,m-1);
    return s;
}
int main()
{
    int m,n;
    scanf("%d %d",&n,&m);
    printf("%I64d\n",dfs(n,m));
    return 0;
}

记忆化搜索:

#include<cstdio>
const long long M=52;
long long dp[M][M];
long long dfs(long long n,long long m)
{
    if(m==1)return dp[n][m]=1;
    if(m==n)return dp[n][m]=1;
    if(n<1)return 0;
    if(dp[n][m])return dp[n][m];
    long long s=0;
    for(int i=1;i<=n-m+1;i++)
        s+=dfs(n-i,m-1);
    return dp[n][m]=s;
}
int main()
{
    int m,n;
    scanf("%d %d",&n,&m);
    printf("%I64d\n",dfs(n,m));
    return 0;
}

DP:

#include<cstdio>
const int M=52;
long long dp[M][M];
int main()
{
    int m,n;
    scanf("%d %d",&n,&m);
    dp[1][1]=1;
    for(int j=1;j<=m;j++)
        for(int i=1;i<=n;i++)
            for(int k=1;k<i;k++)
            {
                if(j==1||i==j){dp[i][j]=1;continue;}
                dp[i][j]+=dp[i-k][j-1];
            }
    printf("%I64d",dp[n][m]);
    return 0;
}

P.S.如果你将dp数组打出来,你会发现这就是杨辉三角(不知道的同学点这里:杨辉三角

1186

这题和上一题很相似,但是它可以分出0来,所以,我们的边界就要改变:

1.当m==n时,就不只有一种分法了,所以这个条件删去;
2.n==1时,还可以分,所以这个条件应该为n==0;
3.m==1时,不可以分了,所以这个条件保留。
其余同上。

同上。

首先,还是dfs:

#include<cstdio>
long long dfs(int n,int m)
{
    if(n==0||m==1)return 1;
    return dfs(n-1,m)+dfs(n,m-1);
}
int main()
{
    int m,n;
    scanf("%d %d",&n,&m);
    printf("%I64d\n",dfs(n,m));
    return 0;
}

记忆化搜索同上题做法:

#include<cstdio>
const int M=32;
long long dp[M][M];
long long dfs(int n,int m)
{
    if(n==0||m==1)return 1;
    if(dp[n][m])return dp[n][m];
    return dp[n][m]=dfs(n-1,m)+dfs(n,m-1);
}
int main()
{
    int m,n;
    scanf("%d %d",&n,&m);
    printf("%I64d\n",dfs(n,m));
    return 0;
}

P.S.如果你将此时的dp数组打出来,你就会发现,这又是一个杨辉三角(只不过旋转了45度而已)。

我们又能得出递推式(就是算杨辉三角的那个)
递推:

for(int i=1;i<=n+1;i++)
    for(int j=1;j<=m+1;j++)
        dp[i][j]=dp[i-1][j]+dp[i][j-1];

顺便说一句,不要忘了初始化!

代码如下:

dfs:

#include<cstdio>
long long dfs(int n,int m)
{
    if(n==0||m==1)return 1;
    return dfs(n-1,m)+dfs(n,m-1);
}
int main()
{
    int m,n;
    scanf("%d %d",&n,&m);
    printf("%I64d\n",dfs(n,m));
    return 0;
}

记忆化搜索:

#include<cstdio>
const int M=32;
long long dp[M][M];
long long dfs(int n,int m)
{
    if(n==0||m==1)return 1;
    if(dp[n][m])return dp[n][m];
    return dp[n][m]=dfs(n-1,m)+dfs(n,m-1);
}
int main()
{
    int m,n;
    scanf("%d %d",&n,&m);
    printf("%I64d\n",dfs(n,m));
    return 0;
}

dp:

#include<cstdio>
const int M=52;
long long dp[M][M];
int main()
{
    int m,n;
    scanf("%d %d",&n,&m);
    for(int i=0;i<=M;i++)
        dp[1][i]=dp[i][0]=1;
    for(int i=1;i<=n+1;i++)
        for(int j=1;j<=m+1;j++)
            dp[i][j]=dp[i-1][j]+dp[i][j-1];
    printf("%I64d\n",dp[n+1][m-1]);
    return 0;
}

最后弱弱地说一句:

如果你想抄程序去CCF上交,请一定记得把输出的“%I64d”改成“%lld”,这6个程序是我在Windows XP系统下做的(如果你在Windows较高版本的系统中,“%lld”或许能过),“%lld”无法编译成功,而评测系统是Linux的,只有“%lld”,你一定要记住!

猜你喜欢

转载自blog.csdn.net/qq_37656398/article/details/74531440