POJ 3181 Dollar Dayz (完全背包)


Dollar Dayz
Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 2275   Accepted: 934

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

Source

 
 

題目鏈接:

http://acm.pku.edu.cn/JudgeOnline/problem?id=3181

題目大意:
輸入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;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM