题目:https://www.luogu.org/problem/P1025
方法一:递推
记d[i][j]为数i按要求分成j份的方案数。
则
1.1 i<j,则d[i][j]=0
1.2 i=j,则d[i][j]=1
1.3 i>j,则先拿出j个1平分,再把i-j按要求分成1,2,...,j份。从而
d[i][j]=d[i-j][1]+d[i-j[2]+...+d[i-j][j-1]+d[i-j][j]
边界:d[i][1]=1,只分出1份,方案数自然是1。
打表进最外层循环为for(int j=2;j<=k;j++),j要从2开始!
对于上面的1.3,进一步处理可得
d[i][j]=d[i-1][j-1]+d[i-j][j]
这个式子有另一种解释:划分中有两类,一类是有1的,一类是没有1的,前者的方案数是d[i-1][j-1],后者的方案数是d[i-j][j]。根据分类讨论的不重不漏,上述正确。
方法二:递归
实例:将7分成3份。
7=1+...//第一份为1的方案数为f[1][6][2]
=2+...//第一份为2的方案数为f[2][5][2]
分析得到第一份不能大于2,如果大于2,比如是3,则后面的数不小于3,则3份的和至少为9。
上面引入f[m][n][k],表示第一份不小于m,将n分成按要求的k份的方案数。可以得到,对于f[m][n][k]而言,它的第一份数不大于n/k,这是上界,且不小于m,这是它的下界。
方法三:dfs
上下界剪枝。第t份数不小于第t-1份数,且不大于余下数值/余下份数。具体见代码。
排队等效冗余。因为前k-1份数确定后,第k份数也随之而确定,从而dfs搜索树只用k-1层,不需要到k层,从而节省大量时间。
不排队冗余,耗时762ms。排队冗余后,耗时28ms。
AC代码一(递推):
#include<cstdio>
#include<iostream>
using namespace std;
int d[201][7];
int main(){
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)d[i][1]=1;//只分成1份,当然方案数为1
for(int i=1;i<=k;i++)d[i][i]=1;
//分成i份,每份都是1,当然方案数为1
//这一行不可缺少,否则,如d[3][3]=d[0][1..3]=0
for(int j=2;j<=k;j++)
for(int i=j;i<=n;i++)//若i<j,则d[i][j]=0
for(int t=1;t<=j;t++)
d[i][j]+=d[i-j][t];
cout<<d[n][k];
return 0;
}
AC代码二(递推):
#include<cstdio>
#include<iostream>
using namespace std;
int d[201][7];
int main(){
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)d[i][1]=1;//只分成1份,当然方案数为1
for(int i=1;i<=k;i++)d[i][i]=1;
//分成i份,每份都是1,当然方案数为1
//这一行不可缺少,否则,如d[3][3]=d[0][1..3]=0
for(int j=2;j<=k;j++)
for(int i=j;i<=n;i++)//若i<j,则d[i][j]=0
d[i][j]=d[i-1][j-1]+d[i-j][j];
cout<<d[n][k];
return 0;
}
AC代码三(递归):
#include<cstdio>
#include<iostream>
using namespace std;
int f(int t,int n,int k){
if(k==1)return 1;
int res=0;
for(int i=t;i<=n-k+1&&i<=n/k;i++)//上下界
res+=f(i,n-i,k-1);
return res;
}
int main(){
int n,k;
cin>>n>>k;
int ans=0;
for(int i=1;i<=n&&i<=n/k;i++)//上下界
ans+=f(i,n-i,k-1);//第一个参数表示第一份数值
cout<<ans;
return 0;
}
AC代码四(dfs):
#include<cstdio>
#include<iostream>
using namespace std;
int n,k;
int s[7],ans=0;
void dfs(int t){
if(t==k){//排除等效冗余
if(n>=s[t-1]) ans++;
return;
}
/*
if(t==k+1){
if(n==0) ans++;
return;
}
*/
for(int i=s[t-1];i<=n/(k-t+1);i++){
//上下界剪枝
s[t]=i;
n-=i;
dfs(t+1);
n+=i;
}
}
int main(){
cin>>n>>k;
dfs(1);
cout<<ans<<"\n";
return 0;
}