P1025 [NOIP 2001 提高组] 数的划分

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;
	
}
posted @ 2026-05-21 08:21  shuiwangrenjia  阅读(9)  评论(0)    收藏  举报