POJ - 3181 Dollar Dayz(多重背包)

Time Limit: 1000MS Memory Limit: 65536KB 64bit IO Format: %lld & %llu

 Status

Description

Farmer John goes to Dollar Days at The Cow Store and discovers an unlimited number of tools on sale. During his first visit, the tools are selling variously for $1, $2, and $3. Farmer John has exactly $5 to spend. He can buy 5 tools at $1 each or 1 tool at $3 and an additional 1 tool at $2. Of course, there are other combinations for a total of 5 different ways FJ can spend all his money on tools. Here they are: 

        1 @ US$3 + 1 @ US$2

        1 @ US$3 + 2 @ US$1

        1 @ US$2 + 3 @ US$1

        2 @ US$2 + 1 @ US$1

        5 @ US$1
Write a program than will compute the number of ways FJ can spend N dollars (1 <= N <= 1000) at The Cow Store for tools on sale with a cost of $1..$K (1 <= K <= 100).

Input

A single line with two space-separated integers: N and K.

Output

A single line with a single integer that is the number of unique ways FJ can spend his money.

Sample Input

5 3

Sample Output

5


一开始就想的的是多重背包,但一直WA,原来是数据可能非常大要用高精度计算,,,,受教了,,,


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 1001;
typedef long long LL;
LL  a[N], b[N];
const LL inf = 1000000000000000000LL;


int main()
{
    int n, k;
    while(scanf("%d %d", &n, &k)!=EOF)
    {
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        b[0]=1;
        for(int i=1;i<=k;i++)
        {
            for(int j=i;j<=n;j+=1)
            {
                a[j]=(a[j]+a[j-i])+(b[j]+b[j-i])/inf;
                b[j]=(b[j]+b[j-i])%inf;
            }
        }
        if(a[n])
            printf("%lld",a[n]);
        printf("%lld\n",b[n]);
    }
    return 0;
}


下面是大神的做法,,但是好像比我的复杂,,,


题目大意:
输入n,和k,问将n用1到k这k个数字进行拆分,有多少种拆分方法。例如:

n=5,k=3 则有n=3+2,n=3+1+1,n=2+1+1+1,n=2+2+1,n=1+1+1+1+1这5种拆分方法

解题思路:

这个题目是个比较明显的动态规划,如果想不到是背包问题,也可以写出状态转移方程如下:

用a[i][j]表示考虑到用数j进行拼接时数字i的拼接方法,可以得到状态转移方程如下:

a[i][j]=a[i][j-1]+a[i-j][j-1]+a[i-2j][j-1]+a[i-3j][j-1]…+a[0][j-1]意思很明显,就将j-1状态可以到达a[i][j]的状态的数字相加。由于得到的结果可能相当大,已经超过了long long,所以应该用大数。但是若跑完所有数据,用大数会超过一秒,我们通过大数的程序可以达到,最大的数字为33位,那么,我们可以将两个long long的数字进行拼接,组成一个超过33位的数。这样增加了速度,这种比较慢的算法也可以不超时。ac的代码如下:

复制代码
复制代码
#include <iostream>
#include<cstdio>
using namespace std;

long long a[1200][200]={0},b[1200][120]={0};

int main()
{
    int i,j,n,m,k;
    long long inf,x;
    inf=1;
    for(i=0;i<18;i++)
    {
        inf=inf*10;
    }
    cin>>n>>m;
    for(i=1;i<=n;i++)
    {
        b[i][1]=0;
        a[i][1]=1;
        for(j=2;j<=m;j++)
        {
            if(j>i)
            {
                a[i][j]=a[i][j-1];
                b[i][j]=b[i][j-1];
                continue;
            }
            a[i][j]=a[i][j-1];
            b[i][j]=b[i][j-1];
            for(k=1;k*j<=i;k++)
            {
                if(i-j*k==0)
                {
                    a[i][j]++;
                    b[i][j]+=a[i][j]/inf;
                    a[i][j]=a[i][j]%inf;
                }
                else {
                    b[i][j]+=b[i-j*k][j-1];
                    a[i][j]+=a[i-j*k][j-1];
                    b[i][j]+=a[i][j]/inf;
                    a[i][j]=a[i][j]%inf;
                }
            }
        }
    }
    if(b[n][m]!=0)
    {
        cout<<b[n][m];
    }
    cout<<a[n][m]<<endl;
    return 0;
}
复制代码
复制代码

 

其实这个题有更快的方法,看上面这个式子a[i][j]=a[i][j-1]+a[i-j][j-1]+a[i-2j][j-1]+a[i-3j][j-1]…+a[0][j-1]我们可以发现,其实可以转到a[i][j]的状态有两种,一种是a[i][j-1]就是不用j这个数字拼接i这个数字的方法数,另一种是a[i-j][j]就是用了j这个数字拼接的到i-j的方法数那么状态转移方程就可以写成a[i][j]=a[i][j-1]+a[i-j][j]不用加那么多项,就降低了一个数量级的复杂度,仍然利用上面处理大数的方法,得到的ac代码如下:

复制代码
复制代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

long long a[1100][110],b[1100][110],inf;

int main(){
    int n,k,i,j;
    for(inf=1,i=0;i<18;i++) inf*=10;
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    scanf("%d%d",&n,&k);
    for(i=0;i<=k;i++) a[0][i]=1;
    for(i=1;i<=k;i++){
        for(j=1;j<=n;j++){
            if(j-i<0){
                b[j][i]=b[j][i-1];
                a[j][i]=a[j][i-1];
                continue;
            }
            b[j][i]=b[j][i-1]+b[j-i][i]+(a[j][i-1]+a[j-i][i])/inf;
            a[j][i]=(a[j][i-1]+a[j-i][i])%inf;
        }
    }
    if(b[n][k]) printf("%I64d",b[n][k]);
    printf("%I64d\n",a[n][k]);
    return 0;
}
复制代码
复制代码

 

其实我们还可以在空间上进行优化,看这个式子a[i][j]=a[i][j-1]+a[i-j][j]我们发现,如果外层循环式j实际上是上一次j在i的值,加上这次j在i-j的值,那么可以只开一维数组,代码如下:

复制代码
复制代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

long long a[1100],b[1100],inf;

int main(){
    int n,k,i,j;
    for(inf=1,i=0;i<18;i++) inf*=10;
    scanf("%d%d",&n,&k);
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    a[0]=1;
    for(i=1;i<=k;i++){
        for(j=1;j<=n;j++){
            if(j-i<0) continue;
            b[j]=b[j]+b[j-i]+(a[j]+a[j-i])/inf;
            a[j]=(a[j]+a[j-i])%inf;
        }
    }
    if(b[n]) printf("%I64d",b[n]);
    printf("%I64d\n",a[n]);
    return 0;
}
复制代码
复制代码

 

 

这实际上是完全背包问题,只是状态转移方程形式有所不同,不过状态转移的方向是完全相同的。for(j=1;j<=k;j++) for(i=1;i<=n;i++) a[i]=a[i]+a[i-j],是这个题目的方法,由于i是从前往后的,那么a[i]前面的a[i-j]已经是已经考虑了j,而如果是for(j=1;j<=k;j++) for(i=n;i>=1;i--) a[i]=a[i]+a[i-j] ;i是从后往前的,那么a[i-j]是没考虑j的,正是一个只能用一次的情形。相似问题的详尽分析,看背包问题九讲:

http://www.cnblogs.com/goodness/archive/2010/08/13/1798801.html

特别注意事项:

此题目是单组测试数据,那么有两种情况,一种是题目没说清楚,实际上是多组(这种情况只能试),一种是真正的单组,但是测试数据的文件特别多。这种情况每个文件会单独跑一次数据,多个文件加起来的时间就是你做这个题用的时间。如果是多组数据,我们一般喜欢打表,但是对于真正的单组数据,打表则是下下策,因为每跑一次就打一遍所有的表,很浪费时间。所以只跑出输入数据需要的结果即可,对于这个题目的第一种解法,如果打表的话,就只能TLE,所以以后遇到真正的单组,一定要注意这个问题。

另外一个需要注意的是关于64位整数的,64位整数的申明可以有__int64和long long两种,编译器都支持,但是对于有些OJ只支持long long,输入输出上可以”%I64d”也可以”%lld”对于Mingw和CodeBlocks只能用%I64d但是,对于有些OJ则只能用%lld,所以比赛之前务必把这个搞清楚。当然,cin和cout就不用考虑这么多了,但是会相对慢些。

另外,这个题用int64的话,需要考虑b不为0,a不够18位的情况,需要加上前导0,这个题数据比较弱,可能是没考虑我这种做法,故没考虑也能ac。

 

 

 

 

 

上面是大牛的解法。

下面是自己写的两种做法:

代码一:模拟高精度加法:

复制代码
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<iostream>
using namespace std;

int dp[1010][100];

void ADD(int n,int m)
{
    for(int i=0;i<60;i++)
    {
        dp[n][i]+=dp[m][i];
        if(dp[n][i]>=10)
        {
            dp[n][i]%=10;
            dp[n][i+1]++;
        }
    }
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        dp[0][0]=1;
        for(int i=1;i<=m;i++)
          for(int j=i;j<=n;j++)
            ADD(j,j-i);
        int t=60;
        while(t>0&&dp[n][t]==0)t--;
        for(int i=t;i>=0;i--)printf("%d",dp[n][i]);
        printf("\n");
    }
    return 0;
}
复制代码

 

 

 

代码二:

完全背包。

用两个long long来实现。

复制代码
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int MAXN=1100;
const long long inf=1000000000000000000LL;

long long a[MAXN];//高位
long long b[MAXN];//低位

int main()
{
    int n,k;
    while(scanf("%d%d",&n,&k)!=EOF)
    {
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(a));
        b[0]=1;
        for(int i=1;i<=k;i++)
          for(int j=i;j<=n;j++)
          {
              a[j]=a[j]+a[j-i]+(b[j]+b[j-i])/inf;
              b[j]=(b[j]+b[j-i])%inf;
          }
        if(a[n]==0)
        {
            printf("%I64d\n",b[n]);
        }
        else
        {
            printf("%I64d%018I64d\n",a[n],b[n]);
        }
    }
    return 0;
}
复制代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值