vijos1060 盒子(重庆一中高2018级信息学竞赛测验7) 解题报告

【问题描述】  
  
  n 个盒子排成一行(编号为1..n)。你有A个红球和B个蓝球。球除了颜色没有任何区别。你可以将球放进盒子。一个盒子可以同时放进两种球,也可以只放一种,也可以空着。球不必全部放入盒子中。编程计算有多少种放置球的方法。 
 
    
 【输入格式】  
  
  一行,n,A,B,用空格分开。
 
    
 【输出格式】  
   
  一行,输出放置方案总数。
 
    
 【输入样例】   
   
2 1 1
 
    
 【输出样例】  
   

 
    
 【样例解释】  
   
  用一对括号表示一个盒子,R表示红色,B表示蓝色,有如下9种方案:
   (  ), (  )
   (R ), (  )
   (B ), (  )
   (RB), (  )
   (R ), (B )
   (B ), (R )
   (  ), (R )
   (  ), (B )
   (  ), (RB) 
 
    
 【数据范围】  
   

1<=n<=20 , 0<=A<=15, 0<=B<=15

答案不超过2^64-1


做题思路(错解):拿到这道题,审题时不仔细,把一个盒子可以同时放进两种球,也可以只放一种看成一个盒子可以同时放进两个球,也可以只放一个,然后就以为一个盒子最多只能放两个球,从而推出了错误的递推方程。


解题思路(正解):根据题意,要求方案总数,自然想到递推算法,但在设计状态函数时有不同的设法。

第一种,也是最容易想到的,求什么设什么,设f(i,j,k)表示前i个盒子最多放j个红球,k个蓝球的方案数,分析前i个盒子时,第i个盒子可以不放球,可以只放一个红球,也可以只放一个蓝球,……,最多可以放j个红球和k个蓝球,此时相对的前i-1个盒子可以最多放j个红球和k个蓝球,可以最多只放j-1个红球和k个蓝球,可以最多只放j个红球和k-1个蓝球,……,最少可以不放球(每种情况分别与前面第i个盒子放球情况相对应),则递推方程为f(i,j,k)=∑f(i-1,j-x,k-y)(0<=x<=j,0<=y<=k)。边界条件为f(0,j,k)=1,即不选盒子,最多放j个红球,k个蓝球的方案数是1(不放球)。答案即为f(n,A,B),该种算法的时间复杂度为O(n*A*B*A*B)。

第二种,是更高级且可以针对更大的数据规模的设法,因为盒子中放红球和放蓝球不相互影响,即假设你先放1个红球,之后你可以放0,1,2,...,B个蓝球,如果你放2个红球,之后你仍可以放0,1,2,...,B个蓝球,所以可以设f(i,j)表示前i个盒子最多放j个球的方案数,分析前i个盒子时,第i个盒子可以不放,可以放1个,最多放j个,此时相对的前i-1个盒子可以最多放j个,可以最多放j-1个,最少可以不放,则递推方程为f(i,j)=∑f(i-1,j-x)(0<=x<=j)。边界条件为f(0,j)=1,即不选盒子,最多放j个球只有一种方案(不放球)。因为对于每个红球的选择,都有B个蓝球的选择,所以答案为f(n,A)*f(n,B),并且在计算f(i,j)时,可以先设一个参数t记录当前f(i-1,j-x)的和,就可以省去枚举x,则此算法的时间复杂度O(n*max(A,B))。

需要注意的是,该题的答案最多为2^64-1(不是2^63-1),不能用long long,要用unsigned long long。


解法1:

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=25;
int N,A,B;
/*
f(i,j,k)表示前i个盒子最多放j个红球,k个蓝球的方案数
f(i,j,k)=f(i-1,j,k)+f(i-1,j-1,k)+f(i-1,j,k-1)+...+f(i-1,0,0) 
边界:f(0,j,k)=1
*/
unsigned long long d[25][20][20];
void solve()  //递推算法
{
	memset(d,0,sizeof(d));  //初始化
	for(int j=0;j<=A;j++)
	for(int k=0;k<=B;k++)
	d[0][j][k]=1;  //边界
	for(int i=1;i<=N;i++)
	for(int j=A;j>=0;j--)
	for(int k=B;k>=0;k--)
	for(int x=0;x<=j;x++)
	for(int y=0;y<=k;y++)
	d[i][j][k]+=d[i-1][j-x][k-y];
	
	cout<<d[N][A][B]<<'\n';
}
int main()
{
	//freopen("box.in","r",stdin);
	//freopen("box.out","w",stdout);
	scanf("%d%d%d",&N,&A,&B);
	solve();
	return 0;
}


解法2:

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=25;
int N,A,B;
/*
f(i,j)表示前i个盒子最多放j个球的方案数
f(i,j)=f(i-1,j)+f(i-1,j-1)+...+f(i-1,0) 
边界:f(0,j)=1
*/
unsigned long long d[25][20];
void solve()  //递推算法
{
	memset(d,0,sizeof(d));  //初始化
	for(int j=0;j<=max(A,B);j++)
	d[0][j]=1;  //边界
	
	for(int i=1;i<=N;i++)
	{
		unsigned long long t=0;  //记录和
		for(int j=0;j<=max(A,B);j++)
		{
			t+=d[i-1][j];
			d[i][j]=t;
		}
	}
	cout<<d[N][A]*d[N][B]<<'\n';
}
int main()
{
	//freopen("box.in","r",stdin);
	//freopen("box.out","w",stdout);
	scanf("%d%d%d",&N,&A,&B);
	solve();
	return 0;
}

考后反思:审题时一定要仔细啊,看错一个字就可能会丢很多分。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值