信息学奥赛一本通 1192 / 1206 / 1222 放苹果 | OpenJudge NOI 2.2 / 2.3 / 2.5 / 2.6 六66 | 洛谷 P2386 放苹果

(131) 2024-05-01 18:01:01

标题不允许有666,这什么规定。。。

【题目链接】

ybt 1206:放苹果
ybt 1222:放苹果
OpenJudge NOI 2.2 666:放苹果
OpenJudge NOI 2.3 666:放苹果
OpenJudge NOI 2.5 666:放苹果
OpenJudge NOI 2.6 666:放苹果
洛谷 P2386 放苹果
注:ybt 1192页面打不开

【题目考点】

1. 递推/递归
2. 搜索

【解题思路】

解法1. 递推
  • 递推状态:a[i][j]:将i个苹果放入j个盘子的方案数
  • 初始状态:
    • 向几个盘子放0个苹果,方案数都为1。因而当i为0时:a[i][j] = 1
    • 如果只有1个盘子,无论有几个苹果,都只能放在这一个盘子里,方案数都为1。因而当j为1时:a[i][j] = 1
    • 如果只有1个苹果,由于各个盘子是相同的,无论放在哪个盘子,都是同一种方案。因而当i为1时:a[i][j] = 1
  • 递推关系:
    要想将i个苹果放入j个盘子,此时考虑是否使用全部j个盘子。
    • 如果苹果数量i小于等于盘子的数量j,那么最多也就可能使用i个盘子,摆放的方案数为将i个苹果放入i个盘子的方案数,即a[i][i]
    • 如果苹果的数量i大于盘子的数量j:
      • 如果不使用全部j个盘子,这种情况下可以将苹果放入前面j-1个盘子,第j个盘子空着。方案数为a[i][j-1]
      • 如果要使用全部j个盘子,那么可以先在每个盘子中放一个苹果,还剩下i-j个苹果。接下来就是将i-j个苹果放入j个盘子的过程,方案数为a[i-j][j]

做题时,由于有t组询问,可以将其看做t次求解同样的问题。也可以先求出所有可能的状态,而后进行t次询问。

解法2:递归

思路与解法1中的类似,递归问题对应递推状态,递归关系对应递推关系,递归出口对应初始状态。
递归问题为:求将i个苹果放入j个盘子的方案数,记作solve(i, j)
递归关系对应递推中的递推关系,递归出口对应递推中的初始状态。将递推的解析中的a[i][j]都改做solve(i, j)看即可。

该题不做记忆化也能过,如果做记忆化递归,那么:
记忆状态与递归状态一致,为将i个苹果放入j个盘子的方案数,记作a[i][j]

解法3:深搜

该题等价于:将一个整数M拆分为n个正整数相加的形式(n <= N),问有多少种拆分方式。
拆分过程中要保证后面拆分出来的数字大于等于前面拆分出来的数字(类似求组合数时的做法)这样才能避免统计重复的情况。

【题解代码】

解法1:递推
  • 处理多组数据方法:当成多次解同一问题
#include<bits/stdc++.h> 
using namespace std;
int main()
{ 
   
	int t, m, n, a[15][15];
	cin >> t;
	while(t--)
	{ 
   
		cin >> m >> n;
		for(int i = 0; i <= m; ++i)
			for(int j = 1; j <= n; ++j)
			{ 
   
				if(i == 0 || i == 1 || j == 1)
					a[i][j] = 1;
				else if(i < j)
					a[i][j] = a[i][i];
				else
					a[i][j] = a[i][j-1] + a[i-j][j];
			}
		cout << a[m][n] << endl;
	}
	return 0; 
}
  • 先求出所有可能的状态,而后进行多次询问。
#include<bits/stdc++.h> 
using namespace std;
int main()
{ 
   
	int t, m, n, a[15][15];
	cin >> t;
	for(int i = 0; i <= 10; ++i)
		for(int j = 1; j <= 10; ++j)
		{ 
   
			if(i == 0 || i == 1 || j == 1)
				a[i][j] = 1;
			else if(i < j)
				a[i][j] = a[i][i];
			else
				a[i][j] = a[i][j-1] + a[i-j][j];
		}
	while(t--)
	{ 
   
		cin >> m >> n;
		cout << a[m][n] << endl;
	}
	return 0; 
}
解法2:递归
  • 一般递归
#include<bits/stdc++.h> 
using namespace std;
int solve(int i, int j)//求将i个苹果放入j个盘子的方案数
{ 
   
	if(i == 0 || i == 1 || j == 1)
		return 1; 
	else if(i < j)
		return solve(i, i);
	else
        return solve(i, j-1) + solve(i-j, j);
}
int main()
{ 
   
	int t, m, n, a[15][15];
	cin >> t;
	while(t--)
	{ 
   
		cin >> m >> n;
		cout << solve(m, n) << endl;
	}
	return 0; 
}
  • 记忆化递归
#include<bits/stdc++.h> 
using namespace std;
int a[15][15];//记忆状态:将i个苹果放入j个盘子的方案数
int solve(int i, int j)//求将i个苹果放入j个盘子的方案数
{ 
   
    if(a[i][j] > 0)
        return a[i][j];
	if(i == 0 || i == 1 || j == 1)
		return 1; 
	else if(i < j)
		return a[i][j] = solve(i, i);
	else
        return a[i][j] = solve(i, j-1) + solve(i-j, j);
}
int main()
{ 
   
	int t, m, n, a[15][15];
	cin >> t;
	while(t--)
	{ 
   
		cin >> m >> n;
		cout << solve(m, n) << endl;
	}
	return 0; 
}
解法3:深搜
#include <bits/stdc++.h>
using namespace std; 
int m, n, ans, numCt;//ans:拆分方式数量 numCt:记录已经拆分了多少数字
void dfs(int a, int st)//对整数a进行一次拆分, 要求拆分出的数字大于等于st
{ 
   
    for(int i = st; i <= a; ++i)//本次拆分,拆分出数字i
    { 
   
        numCt++;
        if(i == a)//如果数字都拆分完了 
            ans++;//得到一组方案 
        else if(numCt < n)//如果已经拆出的数字不足n个,就继续拆 
            dfs(a - i, i);//这次拆分出数字i,下一次拆分的最小数字为i 
        numCt--;//状态还原 
    }
}
int main()
{ 
   
    int t;
    cin >> t;
    while(t--)
    { 
   
        cin >> m >> n;
        ans = 0;//状态初始化。numCt在每次运行后一定为0 
        dfs(m, 1);//对整数m进行一次拆分, 要求拆分出的最小数字是1
        cout << ans << endl;
    }
    return 0;
}
THE END

发表回复