3428.放苹果-AcWing

3428.放苹果:把 M 个同样的苹果放在 N 个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?盘子相对顺序不同,例如 5,1,1 和 1,5,1 算作同一种分法。
解法1:递归划分
int distribute(int apples,int plates);返回apples装入plates中有多少种分配方法。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int distribute(int apples,int plates){
  //递归终止条件
  if(apples==0)return 1;
  if(plates==1)return 1;

  if(apples<plates)return distribute(apples,apples);//苹果数小于盘子数时等价于必然有plates-apples个空盘子,也即将apples放到apples中
  else return distribute(apples-plates,plates)+distribute(apples,plates-1);//对立事件 要么每个盘子都装一个,要么至少有一个盘子不装

}
int main(){
	int plates;
	int apples;
	cout<<"请分别输入苹果和盘子的数量\n";
	cin>>apples>>plates;
	cout<<"分配结果:\n共"<<distributeApples(apples,plates)<<"种分配结果\n"; 
}

解法2:动态规划法
dp[i][j]:表示i个苹果分到j个盘子的分法。则要对边界i=0或者j=1置初值
问:为什么不讨论j=0?答:0个盘子在后续动态转过程中也即for循环中不会被访问:for循环遍历范围为图片的阴影区域。蓝色区域为初始化的边界数据

#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
int main(){
	int plates;
	int apples;
	cout<<"请分别输入苹果和盘子的数量\n"; 
	cin>>apples>>plates;
	
	vector<vector<int>> dp(apples+1,vector<int>(plates+1,0));
	
	//边界条件初始化 
	for(int i=0;i<apples+1;i++)dp[i][1]=1;//不论多少个苹果,当只有一个盘子时分配结果只有一种
	for(int j=0;j<plates+1;j++)dp[0][j]=1;//不论多少个盘子,当只有0个苹果时分配结果只有一种
	
	//动态转移: 
	for(int i=1;i<apples+1;i++)
		for(int j=2;j<plates+1;j++){
			if(i<j)dp[i][j]=dp[i][i];
			else dp[i][j]=dp[i-j][j]+dp[i][j-1];
		}
	
	cout<<"分配结果:\n共"<<dp[apples][plates]<<"种分配结果\n"; 
	return 0;
}

解法3:回溯法求出每一种划分方式

//1,5,1和5,1,1是一样的分法
//那是不是可以用回溯法呢?好像不太行 当前盘子(plateIndex)分入苹果的个数i的可选范围是0~left个 下一个盘子(plateIndex+1)可分入个数为0~left-i个
//但会出现重复的5,0,0和0,5,0和0,0,5 变成了全排列问题,可以对ans去重得到最终结果
//如果想去重可以使用容器 set(利用集合的互异性) 但是vector<int>相同的定义是对应下标元素相同 
//所以可以先对path排序再将path(vector<int> path)在插入ans(set<vector<int>> ans) 
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
#include<set>
using namespace std;
set<vector<int>> ans;
int plates;
int apples;
//platesIndex为当前处理盘子的下标,若共有5个盘子则取值为0~4
//distribution[]表示当前的分配情况 每个元素初值为0
void backtracking(int plateIndex, vector<int> distribution, int& left) {
	if (plateIndex >= plates || left == 0) {
		if (left == 0) {
                        //此处对distribution进行了排序,所以函数传入的参数distribution不能使用引用类型
                        //一开始就是想着节省空间而使用的引用类型导致输出了错误的结果
			sort(distribution.begin(), distribution.end());
                        //利用了set元素的特异性实现了去重
			ans.insert(distribution);
		}
		return;
	}
	for (int i = 0; i <= left; i++) {
		if (left - i < 0)break;
		else {
			distribution[plateIndex] = i;
			left -= i;
			backtracking(plateIndex + 1, distribution, left);
			distribution[plateIndex] = 0;
			left += i;
		}
	}
}
int main() {
	cout << "请分别输入苹果和盘子的数量\n";
	cin >> apples >> plates;
	vector<int> distribution(plates, 0);
	backtracking(0, distribution, apples);
	cout << "共有" << ans.size() << "种分配方式\n";
	for (auto c : ans) {
		for (auto a : c)
			cout << a << " ";
		cout << '\n';
	}
}

解法四:同样使用回溯法但是不需要使用set集合,也不需要排序操作
使用的是: 可以重复用i,但是不能回头用0~i-1也即相对于原始for(i=0;i<=left;i++)变为现在的for(i=i;i<=left;i++)使后面的盘子分的个数至少是i(看for循环开始的下标和backtracking传入的参数)

#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
#include<set>
using namespace std;
vector<vector<int>> ans;
int plates;
int apples;
//platesIndex为当前处理盘子的下标,若共有5个盘子则取值为0~4
//distribution[]表示当前的分配情况 每个元素初值为0
void backtracking(int plateIndex,int i,vector<int> distribution,int &left) {
	if(plateIndex>=plates||left==0) {
                //一开始此处仅使用left==0作ans更新的判断条件,会爆错。输出如下图4.1。而加入plateIndex>=plates后正确输出的原因是“重复用i但不回头”方法的
                //含义为:后续的每次分配至少是i从而减少了i从0处增长而导致重复分配。但在为第一个盘子分配时,若for循环i=5,left变为0,再次递归,
                //会提前进入终止条件(此时的plateIndex为0,没有为后面的盘子(标号为的1~paltes-1的盘子)分配苹果),
                //且符合更新finalPath的条件从而插入了重复答案。因此改变finalPath也即ans的更新条件即可解决上述问题。
		if(left==0&&plateIndex>=plates) {
			ans.push_back(distribution);
		}
		return;
	}
	for(; i<=left; i++) {
		if(left-i<0)break;
		else {
			distribution[plateIndex]=i;
			left-=i;
			backtracking(plateIndex+1,i,distribution,left);
			distribution[plateIndex]=0;
			left+=i;
		}
	}
}
int main() {
	cout<<"请分别输入苹果和盘子的数量\n";
	cin>>apples>>plates;
	vector<int> distribution(plates,0);
	backtracking(0,0,distribution,apples);
	cout<<"共有"<<ans.size()<<"种分配方式\n";
	for(auto c:ans){
		for(auto a:c)
			cout<<a<<" ";
		cout<<'\n';
	}
}

图4.1(错误输出):

posted @ 2025-03-23 16:36  Cheauncey  阅读(113)  评论(0)    收藏  举报