【递推】【vijos1060】盒子

来源:互联网 发布:mac os 未能验证更新 编辑:程序博客网 时间:2024/06/06 00:31

P1060盒子
Accepted
描述

N个盒子排成一行(1<=N<=20)。你有A个红球和B个蓝球。0 <= A <= 15, 0 <= B <= 15。球除了颜色没有任何区别。你可以将球放进盒子。一个盒子可以同时放进两种球,也可以只放一种,也可以空着。球不必全部放入盒子中。编程计算有多少种放置球的方法。
格式
输入格式

一行,N,A,B,用空格分开。
输出格式

一行,输出放置方案总数。
样例1
样例输入1

2 1 1

样例输出1

9

限制

1s

方案数,又是方案数!
题目大意:给你n个排成一排的盒子,每个盒子里面可以不放入,单放入一些红球或者放入一些蓝球,同时放入两种球(都是若干个,注意原题说的是放入两种球不是最多只能放两个球哦)。然后要求的是满足最后用的红球不超过A个,蓝球不超过B个的方案数。
算法一:根据原题所说,用回溯暴力的模拟装球的过程,每考虑完所有的盒子方案数就叠加1。时间复杂度不稳定 最坏是O((AB)^N) 预期得分25~30
算法一代码如下:

#include<cstdio>#include<iostream>#include<cstring>#include<queue>#include<algorithm>using namespace std;typedef unsigned long long LL;const int maxn=25;int n,A,B;bool vis[maxn][maxn][maxn];LL ans=0;void run(int i,int j,int k)//当前考虑第i个盒子的放置情况,此时已经放了j个红球,k个蓝球。{    if(i>n)    {        ans++;        return;    }    for(int a=0;a<=A-j;a++)    for(int b=0;b<=B-k;b++)//if(!vis[i][a][b])    {        //vis[i][a][b]=1;        run(i+1,j+a,k+b);        //vis[i][a][b]=0;    }}int main(){    freopen("box.in","r",stdin);    freopen("box_dp.out","w",stdout);    scanf("%d%d%d",&n,&A,&B);    run(1,0,0);//最先考虑第一个盒子的情况    cout<<ans<<endl;    return 0;}

算法二:方案问题,我们很容易想到递推,根据本题的要求和”求什么就设什么”的原则比较容易想到状态函数f(i,j,k)表示用不超过j个红球,不超过k个蓝球放前i个盒子的方案数。
分析第i个盒子的选择,显然第i个盒子可以不放球 或者 单放一个红球 或者 单放两个红球 … 或者 单放i个红球 或者 单放一个蓝球 或者 单放两个蓝球 … 或者 单放j个蓝球 或者 放一个红球一个蓝球 …
根据状态转移的思想 写出递推方程:
f(i,j,k)=f(i-1,j,k)+f(i-1,j,k-1)+…+f(i-1,j,0)
+f(i-1,j-1,k)+f(i-1,j-1,k-1)+…+f(i-1,j-1,0)
+…
+f(i-1,0,k)+f(i-1,0,k-1)+…+f(i-1,0,0)
即f(i,j,k)+=f(i-1,x,y)| 0<=x<=j 0<=y<=k
边界是f(0,j,k)=1
时间复杂度O(A^2*B^2*n) 预期得分100
算法二的代码实现:

#include<cstdio>#include<iostream>#include<cstring>#include<queue>#include<algorithm>using namespace std;typedef unsigned long long LL;const int maxn=25;int n,A,B;LL f[maxn][maxn][maxn];/*    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,j-1,k-1)+...    f(0,i,j)=1; */int main(){    freopen("box.in","r",stdin);    freopen("box.out","w",stdout);    scanf("%d%d%d",&n,&A,&B);    for(int a=0;a<=A;a++)    for(int b=0;b<=B;b++)    f[0][a][b]=1;    for(int i=1;i<=n;i++)    for(int j=0;j<=A;j++)    for(int k=0;k<=B;k++)    for(int a=0;a<=j;a++)    for(int b=0;b<=k;b++)    f[i][j][k]+=f[i-1][j-a][k-b];    cout<<f[n][A][B]<<'\n';    return 0;}

算法三:优化递推
由于A,B两球除了颜色之外其余的属性都相同,不如将两种球分别处理。
此时设f(i,j)表示选不超过j个球放入前i个盒子。
假设只有A球 f(n,A)就是n个盒子,A个红球的方案数。
假设只有B球 f(n,B)就是n个盒子,B个红球的方案数。
由乘法原理,最后的答案是f(n,A)*f(n,B)
同样分析第i个盒子的情况,可以不选球,可以选一个,可以选两个…
对应的递推方程:f(i,j)=f(i-1,j)+f(i-1,j-1)+f(i-1,j-2)+…+f(i-1,0)=sigma(f(i-1,x)) | 0<=x<=j
边界分析 f(0,j)=1
这种方法的时间复杂度O(n*max(A,B)^2)
实现递推的代码:

    for(int j=0; j<=A || j<=B ;j++)    f[0][j]=1;    for(int i=1;i<=n;i++)    for(int j=0; j<=A || j<=B ;j++)    for(int k=0;k<=j;k++)    {        f[i][j]+=f[i-1][k];    }

观察到填表实现时f[i][j]的值其实是上一行所有f[i-1][j]的和,还可以继续用前缀和的思想优化,在枚举j的时候就算出t,然后再改变f[i][j]的值。
完整代码:

#include<cstdio>#include<iostream>#include<cstring>#include<queue>#include<algorithm>using namespace std;typedef unsigned long long LL;const int maxn=25;int n,A,B;LL f[maxn][maxn];/*    f(i,j)=在前i个盒子里面放不多于j个球的方案数     f(i,j)=f(i-1,j)+f(i-1,j-1)+...+f(i-1,0);    Ans=f(n,A)*f(n*B);    f(0,j)=1; |  0<=j<=max(A,B)*/int main(){    freopen("box.in","r",stdin);    freopen("box_3.out","w",stdout);    scanf("%d%d%d",&n,&A,&B);    for(int j=0; j<=A || j<=B ;j++)    f[0][j]=1;    for(int i=1;i<=n;i++)//观察到f(i,j)=sigma(f(i-1,x)) | 0<=x<=j  填表的时候只用上面一行,所以前缀和优化     {        LL t=0;        for(int j=0; j<=A || j<=B ;j++)        {            t+=f[i-1][j];            f[i][j]=t;        }    }    cout<<f[n][A]*f[n][B]<<'\n';    return 0;}

考试的总结:考的时候,只想到了第二种方法,数据也小,确实也能过。今天听评讲,Mr.he提了一个问题,如果n能到1000,A,B都能到500,对方案要取模怎么办。联想的考的第二个题,这样搞绝对要爆内存(128M),结果Mr.he讲了这个第三种优化方法,直接开二维就行了,时间也比三维的快,虽然写了搜索对拍过了,我觉得我还是没做到一题多解,看来还是要多想啊!

0 1
原创粉丝点击