目录
题目描述
将整数 n 分成 k 份,且每份不能为空,任意两个方案不相同(不考虑顺序)。
例如:n=7,k=3,下面三种分法被认为是相同的。
1,1,5;
1,5,1;
5,1,1.
问有多少种不同的分法。
输入格式
n,k (6<n≤200,2≤k≤6)
输出格式
11 个整数,即不同的分法。
输入输出样例
输入 #1
7 3
输出 #1
4
说明/提示
四种分法为:
1,1,5;
1,2,4;
1,3,3;
2,2,3.
【题目来源】
NOIP 2001 提高组第二题
解法
1.暴力
这道题最直接的方法就是暴力求解。我们可以想到,我们可以枚举前 k-1 个数,这样最后一个数自然而然地就出来了。同时,我们可以试着让 k-1 个数变成上升序列,这样就避免了重复。就是
for(int i=1;i<=n/2;i++)
{
if(i<=n-i)
{
ans++;
}
}
为什么要小于等于 n/2 呢?因为如果 i 大于了 n/2 ,那么后面的数就都会小于 i ,就不满足上升条件了。
代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,k;
cin>>n>>k;
int ans=0;
if(k==1)
{
cout<<1;
}
else if(k==2)
{
for(int i=1;i<=n/2;i++)
{
if(i<=n-i)
{
ans++;
}
}
}
else if(k==3)
{
for(int i=1;i<=n/2;i++)
{
for(int j=i;j<=n/2;j++)
{
if(i<=n-i-j&&j<=n-i-j)
{
ans++;
}
}
}
}
else if(k==4)
{
for(int i=1;i<=n/2;i++)
{
for(int j=i;j<=n/2;j++)
{
for(int k=j;k<=n/2;k++)
{
if(i<=n-i-j-k&&j<=n-i-j-k&&k<=n-i-j-k)
{
ans++;
}
}
}
}
}
else if(k==5)
{
for(int i=1;i<=n/2;i++)
{
for(int j=i;j<=n/2;j++)
{
for(int k=j;k<=n/2;k++)
{
for(int l=k;l<=n/2;l++)
{
if(i<=n-i-j-k-l&&j<=n-i-j-k-l&&k<=n-i-j-k-l&&l<=n-i-j-k-l)
{
ans++;
}
}
}
}
}
}
else if(k==6)
{
for(int i=1;i<=n/2;i++)
{
for(int j=i;j<=n/2;j++)
{
for(int k=j;k<=n/2;k++)
{
for(int l=k;l<=n/2;l++)
{
for(int o=l;o<=n/2;o++)
{
if(i<=n-i-j-k-l-o&&j<=n-i-j-k-l-o&&k<=n-i-j-k-l-o&&l<=n-i-j-k-l-o&&o<=n-i-j-k-l-o)
{
ans++;
}
}
}
}
}
}
}
cout<<ans;
return 0;
}
2.DFS
当然,这道题也可以用DFS做。普通的DFS:
#include<bits/stdc++.h>
using namespace std;
int n,k;
int ans=0;
void dfs(int nn,int s,int sum)
{
if(sum>n)
{
return ;
}
if(nn>k)
{
if(sum==n)
{
ans++;
}
return ;
}
for(int i=s;i<=n;i++)
{
dfs(nn+1,i,sum+i);
}
}
int main()
{
cin>>n>>k;
dfs(1,1,0);
cout<<ans;
return 0;
}
但是有一点,就是这段代码会超时,那么我们要想一想要怎么优化一下代码。因为我们前面说过,枚举的数是递增趋势的,所以前一个数要比后一个数小,如果它们两个一样了,同时加上前面的数比 n 大了,我们就不需要继续枚举了。也就是 i*(k-nn+1)+sum<=n 。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,k;
int ans=0;
void dfs(int nn,int s,int sum)
{
if(sum>n)
{
return ;
}
if(nn>k)
{
if(sum==n)
{
ans++;
}
return ;
}
for(int i=s;i*(k-nn+1)+sum<=n;i++)
{
dfs(nn+1,i,sum+i);
}
}
int main()
{
cin>>n>>k;
dfs(1,1,0);
cout<<ans;
return 0;
}
3.DP
其实这道题也可以看作是排列组合的题。我们把 n 看作是 n 个小球, k 看作是 k 个盒子,题目是让你求“将 n 个小球放进 k 个盒子里,同时不允许有空盒,且盒子与小球都不加区分”的方案数。
那么我们可以看出现在将 n 个小球放进 k 个盒子里的方案总数等于:
①至少有一个盒子,里面只有一个小球的方案数 + ②没有盒子里面只有一个小球的方案数
我们设 为 “将 n 个小球放进 k 个盒子里,同时不允许有空盒”的方案数。
那么,情况①的方案数(盒子都是一样的)就是“将 n-1 个小球放进 k-1 个盒子里,同时不允许有空盒,且盒子与小球都不加区分”的方案数。
对于情况②的方案数,我们把每个盒子里都拿出一个小球,也就是“将 n-k 个小球放进 k 个盒子里,同时不允许有空盒,且盒子与小球都不加区分”的方案数。
那么,我们就能列出状态转移方程:
f[ i ][ j ] = f[ i - 1 ][ j - 1 ] + f[ i - j ][ j ]
代码:
#include<bits/stdc++.h>
using namespace std;
int f[210][210];
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
f[i][1]=1;
}
for(int i=2;i<=n;i++)
{
for(int j=2;j<=k;j++)
{
f[i][j]=f[i-1][j-1]+f[i-j][j];
}
}
cout<<f[n][k];
return 0;
}