P1025 [NOIP 2001 提高组] 数的划分
解题思路
方法一:DFS
一步步枚举下一个可选的数。
#include<iostream>
using namespace std;
int n,k;
long long ans = 0;
void dfs(int remain,int cnt,int start)
{
if(cnt == 1)
{
ans ++;
return ;
}
for(int i = start ; i <= remain/cnt ; i ++)
{
dfs(remain - i , cnt - 1 , i);
}
}
int main()
{
cin>>n>>k;
dfs(n,k,1);
cout<<ans <<endl;
return 0;
}
这里有几个要点:
- 为了保证不重复选数,我们要求后面的数大于等于前面的数,所以i的范围是start到remain/cnt,start是最小值,而最大值不能超过平均数,否则后续一定会有一个数小于前面的数。比如7分三个,前面的数最多是2,如果前面有个数为3,后面一定会有一个数小于3。同时还可以这么理解i * cnt <= remain;这个for循环保证了选数的合理性,所以不需要再对不合法状态剪枝了。
- 当cnt等于1,说明只剩下一个位置,且剩下的数一定合法,所以直接让答案加一,否则会进入不需要的调用,增加时间开销。这个剪枝能提420ms
算法二:动态规划
dp[n][k]表示n分为k个数的种数。状态转移方程为:dp[i][j] = dp[i-1][j-1] + dp[i-j][j],含义为:分为两种情况,一种是分法中至少包含一个1,另一种是所有数都大于等于2。
当至少有一个数为1时,就是将i-1分为j-1份,当所有数都大于1时,每个数减1,就变成了i-j,再分为j份。
#include<iostream>
using namespace std;
long long dp[210][10];
int n,k;
int main()
{
cin>>n>>k;
for(int i = 1 ; i <= n ;i ++)
{
dp[i][1] = 1;
}
for(int i = 2 ; i <= n; i ++)
{
for(int j = 2; j <= k ; j++)
{
dp[i][j] = dp[i-1][j-1];
if(i >= j) dp[i][j] += dp[i-j][j];
}
}
cout<<dp[n][k]<<endl;
return 0;
}

浙公网安备 33010602011771号