【递推】【vijos1060】盒子

4 篇文章 0 订阅
4 篇文章 0 订阅

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讲了这个第三种优化方法,直接开二维就行了,时间也比三维的快,虽然写了搜索对拍过了,我觉得我还是没做到一题多解,看来还是要多想啊!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值