n 个盒子排成一行(编号为1..n)。你有A个红球和B个蓝球。球除了颜色没有任何区别。你可以将球放进盒子。一个盒子可以同时放进两种球,也可以只放一种,也可以空着。球不必全部放入盒子中。编程计算有多少种放置球的方法。
【输入格式】
一行,n,A,B,用空格分开。
【输出格式】
一行,输出放置方案总数。
【输入样例】
2 1 1
【输出样例】
9
【样例解释】
用一对括号表示一个盒子,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;
}
考后反思:审题时一定要仔细啊,看错一个字就可能会丢很多分。