题目
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”,你一定要记住!