基礎揹包問題的一些題目!!

果7發表於2014-01-19

hdu1203

題目地址:http://acm.hdu.edu.cn/showproblem.php?pid=1203

每件物品的cos為a[i],價值與b[i]有關,是一個簡單的01揹包問題。

AC程式碼:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;

double dp[10005];
int a[10005];
double b[10005];

int main()
{
    int n,m,i,j;
    while(cin>>n>>m&&n+m)
    {
        for(i=0;i<m;i++)
            cin>>a[i]>>b[i];

        memset(dp,0,sizeof(dp));
        for(i=0;i<m;i++)
            for(j=n;j>=a[i];j--)
                dp[j]=max(dp[j],1-(1-dp[j-a[i]])*(1-b[i]));

        double res=dp[n]*100;
        printf("%.1f%%\n",res);
    }
    return 0;
}



hdu2602

題目地址:http://acm.hdu.edu.cn/showproblem.php?pid=2602

典型的01揹包,直接見程式碼:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int dp[1002],i,j,num,vol;
int cos[1002],wei[1002];

void ZeroOnePack(int cost,int weight)
{
    for(j=vol;j>=cost;j--)
        if(dp[j]<dp[j-cost]+weight)
           dp[j]=dp[j-cost]+weight;
}

int main()
{
   int tes,n;
   cin>>tes;
   while(tes--)
   {
       cin>>n>>vol;
       for(i=0;i<n;i++)
           scanf("%d",&cos[i]);
       for(i=0;i<n;i++)
           scanf("%d",&wei[i]);
       memset(dp,0,sizeof(dp));
       for(i=0;i<n;i++)
          ZeroOnePack(wei[i],cos[i]);
       cout<<dp[vol]<<endl;
   }
   return 0;
}


hdu 1171

題目地址:http://acm.hdu.edu.cn/showproblem.php?pid=1171

有n件物品,每件物品的價值和數量不同,是一個完全揹包問題,由於數量並不是很大,可以將它轉換為01揹包問題。

AC程式碼:

//用兩個包使得裝得總量差儘量小
//就找一個總量sum/2的包看最多能裝多少
//物品最多有n*m<=5000個.箱子開5000*50/2<2*10^5
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

int dp[200005];
int wei[5005];

int main()
{
    int t,i,j,n,v,m,sum;
    while(cin>>t&&t>0)
    {
        n=sum=0;
        memset(dp,0,sizeof(dp));
        for(i=0;i<t;i++)
        {
            cin>>v>>m;
            sum+=v*m;
            while(m--)
                wei[n++]=v;
        }

        for(i=0;i<n;i++)
            for(j=sum/2;j>=wei[i];j--)
                dp[j]=max(dp[j],dp[j-wei[i]]+wei[i]);

        cout<<sum-dp[sum/2]<<" "<<dp[sum/2]<<endl;
    }
    return 0;
}


hdu1864

題目地址:http://acm.hdu.edu.cn/showproblem.php?pid=1864

貌似是個揹包問題,不過揹包需要用到int,由於答案需要保留兩位有效數字,所以將揹包的cos放大100倍,把揹包的容量也擴大一百倍,不過這樣多少會有點精度的損失。01揹包。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;

double wei[35],dp[3000005];

int main()
{
    double sum,sa,sb,sc,money;
    int t,n,q,i,j;
    int flag;
    char c;
    while(cin>>sum>>t&&t)
    {
        n=0;
        memset(dp,0,sizeof(dp));
        while(t--)
        {
            cin>>q;
            sa=sb=sc=0;  //分別記錄每張發票的A,B,C價錢
            flag=0;
            while(q--)
            {
                scanf(" %c:%lf",&c,&money);
                if(c<'A'||c>'C'||money>600)   //不可報銷或單項物品金額>600
                    flag=1;
                if(c=='A') sa+=money;
                else if(c=='B') sb+=money;
                else if(c=='C') sc+=money;
            }
            if(!flag&&sa+sb+sc<=1000&&sa<=600&&sb<=600&&sc<=600)
                wei[n++]=sa+sb+sc;
        }

        int isum=sum*100;
        for(i=0;i<n;i++)
        {
            int iwei=wei[i]*100;
            for(j=isum;j>=iwei;j--)    //同時擴大100倍
                dp[j]=max(dp[j],dp[j-iwei]+wei[i]);
        }

        printf("%.2lf\n",dp[isum]);
    }
    return 0;
}
/*
200.00 3
2 A:23.50 B:100.00
1 C:650.00
3 A:59.99 A:120.00 X:10.00
1200.00 2
2 B:600.00 A:400.00
1 C:200.50
1200.50 3
2 B:600.00 A:400.00
1 C:200.50
1 A:100.00
100.00 0
1000 4
1 A:300
1 B:300
1 A:200
1 C:500
*/

hdu1712

題目地址:http://acm.hdu.edu.cn/showproblem.php?pid=1712

題目說的意思是有n個作業,如果用j天去完成第i項作業,那麼會得到價值wei[i][j],因此每項任務要麼不完成,要麼只能完成一次,是一個完全揹包的問題,可以先看下揹包九講的內容P06.

for 所有的組k

    for v=V..0

        for 所有的i屬於組k

            f[v]=max{f[v],f[v-c[i]]+w[i]}

意思就是說每個組裡面最多隻能選一項。


AC程式碼:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

int wei[105][105];
int dp[105];

int main()
{
    int n,m,i,j,k;
    while(cin>>n>>m&&n+m)
    {
        memset(dp,0,sizeof(dp));
        for(i=1;i<=n;i++)
            for(j=1;j<=m;j++)
                scanf("%d",&wei[i][j]);

        for(i=1;i<=n;i++)   //分成的n組
            for(j=m;j>=0;j--)   //總共用j天
                for(k=1;k<=m;k++)   //所有的k屬於組i
                    if(j>=k)
                        dp[j]=max(dp[j],dp[j-k]+wei[i][k]);
        cout<<dp[m]<<endl;
    }
    return 0;
}

hdu2660

題目地址:http://acm.hdu.edu.cn/showproblem.php?pid=2660

題目先給你一個n,k,有n個物品,下面依次是n個物品的cos和wei,然後是揹包的容量是vol,不過有個條件揹包裝的物品數目得不多於k,因此需要用到二維的01揹包。dp[l][j]表示容量為l,最多裝j個物品時候裝得最大價值。狀態轉移方程為 dp[l][j]=max(dp[l][j],dp[l-cos[i]][j-1]+wei[i])


AC程式碼:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int dp[1002][21],i,j,vol,k,l;
int cos[1002],wei[1002];

int main()
{
   int T;
   cin>>T;
   while(T--)
   {
       memset(dp,0,sizeof(dp));
       int n;
       scanf("%d%d",&n,&k);
       for(i=0;i<n;i++)
           scanf("%d%d",&wei[i],&cos[i]);
       scanf("%d",&vol);

       for(i=0;i<n;i++)
          for(l=vol;l>=cos[i];l--)
              for(j=1;j<=k;j++)
                 dp[l][j]=max(dp[l][j],dp[l-cos[i]][j-1]+wei[i]);

       printf("%d\n",dp[vol][k]);
   }
   return 0;
}



hdu1709

題目地址:http://acm.hdu.edu.cn/showproblem.php?pid=1709

題目大意:給定幾個數字,作為砝碼,看哪些不能被稱出。
解題思路:先01揹包求出可以相加的所有的情況,找出可以被稱出的重量,再暴力找出可以使用減法的情況。


AC程式碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[10005];
int vis[10005];
int xx[10005];
int res[10005];
int a[105],sum,n,t;

void onepack(int cost,int weight)
{
	int l;
    for(l=sum;l>=cost;l--)
		if(dp[l]<dp[l-cost]+weight)
           dp[l]=dp[l-cost]+weight;
}

int main()
{
   int i,j;
   while(~scanf("%d",&n))
   {
	  memset(dp,0,sizeof(dp));
	  memset(vis,0,sizeof(vis));
	  sum=0;
      for(i=0;i<n;i++)
	  {
		  scanf("%d",&a[i]);
		  sum+=a[i];
	  }

	  for(i=0;i<n;i++)
         onepack(a[i],a[i]);

	  for(i=1;i<=sum;i++)
		  vis[dp[i]]=1;
      //先揹包求出所有和能組成的

	  t=0;
	  for(i=1;i<=sum;i++)
		  if(vis[i])
			  xx[t++]=i;
      //把揹包求出的可行值都存放入xx陣列中

	  for(i=0;i<t;i++)
		for(j=i+1;j<t;j++)
			vis[xx[j]-xx[i]]=1;   //用一次減法可以求出的

	  t=0;
	  for(i=1;i<=sum;i++)
          if(!vis[i])
		     res[t++]=i;
	  printf("%d\n",t);

	  if(t>0)
	  {
         for(i=0;i<t-1;i++)
			 printf("%d ",res[i]);
		 printf("%d\n",res[t-1]);
	  }
   }
   return 0;
}


poj3624

題目地址:http://poj.org/problem?id=3624

標準的01揹包。。。

白天斷電斷網,一血也沒了。。

AC程式碼:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

int dp[15000];
int cos[5000],wei[5000];

int main()
{
    int i,j,n,vol;
    while(cin>>n>>vol)
    {
        memset(dp,0,sizeof(dp));
        for(i=0;i<n;i++)
            cin>>cos[i]>>wei[i];
        for(i=0;i<n;i++)
            for(j=vol;j>=cos[i];j--)
                dp[j]=max(dp[j],dp[j-cos[i]]+wei[i]);
        cout<<dp[vol]<<endl;
    }
    return 0;
}


hdu 2159

題目地址:http://acm.hdu.edu.cn/showproblem.php?pid=2159

題目大意:中文題目,就不做解釋了,是一個二維完全揹包的標準題目。

AC程式碼:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

int dp[105][105];
int wei[105],cos[105];

int main()
{
    int i,j,k;
    int n,m,K,s;
    while(cin>>n>>m>>K>>s)
    {
        for(i=0;i<K;i++)
            scanf("%d%d",&wei[i],&cos[i]);
        memset(dp,0,sizeof(dp));

        for(i=0;i<K;i++)
        {
            for(j=1;j<=m;j++)
            {
                for(k=1;k<=s;k++)
                {
                    if(j>=cos[i])
                        dp[j][k]=max(dp[j][k],dp[j-cos[i]][k-1]+wei[i]);
                }
            }
        }

        if(dp[m][s]<n)
        {
            puts("-1");
            continue;
        }

        int res=m;
        for(i=1;i<=m;i++)
            for(j=1;j<=s;j++)
            {
                if(dp[i][j]>=n)
                    res=min(res,i);
            }
        res=m-res;
        cout<<res<<endl;
    }
    return 0;
}

/*
10 10 1 10
1 1
10 10 1 9
1 1
9 10 2 10
1 1
2 2
*/


hdu 2844&&poj  1742

題目地址:http://acm.hdu.edu.cn/showproblem.php?pid=2844

多重揹包,題目意思,是給n種金幣的金額,金額不能超過m,然後輸入每種金幣的v[i],c[i]分別代表這種金幣代表的金額和這種金幣的數量。需要輸出這些金幣組合能達到不超過m的金額是多少種。

典型的二維多重揹包題目。


如果採取普通的多重揹包的解法,那麼是O(n*m*c)
n是物品的種類數目,m是揹包的容量,c是物品的每種種類相應的數目
如果應用這個題目,那麼是10^7*10^3=10^10的時間複雜度,當然不可行。
當然我們可以將複雜度降到O(n*m),不過我們需要多開一個陣列記錄每次用物品的個數
這個題目需要求解的是能組成多少<=m的總量。
具體見程式碼。


#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=100005;

int visi[maxn];
int used[maxn];
int v[105],c[105];

int main()
{
    int n,m;
    int i,j;
    while(cin>>n>>m&&n+m)
    {
        for(i=0;i<n;i++)
            cin>>v[i];
        for(i=0;i<n;i++)
            cin>>c[i];

        int res=0;
        memset(visi,0,sizeof(visi));
        visi[0]=1;
        for(i=0;i<n;i++)
        {
            memset(used,0,sizeof(used));
            for(j=v[i];j<=m;j++)
            {
                if(!visi[j]&&visi[j-v[i]]&&used[j-v[i]]<c[i])
                {
                    visi[j]=1;
                    used[j]=used[j-v[i]]+1;
                    res++;
                }
            }
        }

        cout<<res<<endl;
    }
    return 0;
}

/*
3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0
*/



hdu 2191

題目地址:http://acm.hdu.edu.cn/showproblem.php?pid=2191

題目大意:給你揹包的容量vol,然後給你n種物品,每種物品有它的價值,體積,當然還有每種物品的數量。典型的多重揹包,由於種類最多為100,每種物品數量最多為20,如果轉換為01揹包總量最多為2*10^3然後再乘上揹包容量100,10^5的時間複雜度,可以接受。

AC程式碼:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

int cos[2005],wei[2005];
int dp[105];

int main()
{
    int tes;
    cin>>tes;
    int i,j;
    while(tes--)
    {
        int vol,m;
        cin>>vol>>m;
        int num=0;
        int a,b,c;
        for(i=0;i<m;i++)
        {
            cin>>a>>b>>c;
            for(j=0;j<c;j++)    //多重揹包轉換為01揹包
            {
                cos[num]=a;
                wei[num++]=b;
            }
        }

        memset(dp,0,sizeof(dp));
        for(i=0;i<num;i++)
            for(j=vol;j>=cos[i];j--)
                dp[j]=max(dp[j],dp[j-cos[i]]+wei[i]);

        cout<<dp[vol]<<endl;
    }
    return 0;
}

/*
1
8 2
2 100 4
4 100 2
*/


poj 2392      完全揹包

題目地址:http://poj.org/problem?id=2392

題目大意:給你k種石頭,每種石頭有自己的高度h,和最高能呆的高度a,還有數量c,問用這些石頭堆起來最高的高度。

我們需要對這些石頭的最高能呆的高度從低到高排個序,然後就直接用完全揹包的套路就可以了。

例如

2

8  15  1

3   8    3

排序之後最大高度是14,不排序的最大高度是8.

AC程式碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;

int dp[40005];
int num[40005];

struct node
{
    int h;
    int a;
    int c;
}block[405];

bool cmp(node p1,node p2)
{
    return p1.a<p2.a;
}

int main()
{
    int k,i,j;

    while(cin>>k)
    {
        memset(dp,0,sizeof(dp));
        dp[0]=1;
        for(i=0;i<k;i++)
            cin>>block[i].h>>block[i].a>>block[i].c;

        sort(block,block+k,cmp);

        int ans=0;
        for(i=0;i<k;i++)
        {
            memset(num,0,sizeof(num));   //記錄用了多少個石頭的
            for(j=block[i].h;j<=block[i].a;j++)
            {
                if(!dp[j]&&dp[j-block[i].h]&&num[j-block[i].h]<block[i].c)
                {
                    dp[j]=1;
                    num[j]=num[j-block[i].h]+1;
                    ans=max(ans,j);
                }
            }
        }

        cout<<ans<<endl;
    }
    return 0;
}


poj 2184經典的01揹包

題目地址:http://poj.org/problem?id=2184

題目的意思是給你一些牛的s值和f值,讓你在這些牛選取一些牛使得在保證s值之和>=0,f值之和>=0的基礎上使得所有的s值和f值之和最大。

首先看到這個題目就會想到揹包,而且是01揹包,但是中間有負數,處理的方法就是採取手動的移位,所有的都往右移動即可。我們所要做的就是用dp[s]揹包來背f,就是s一定的情況下使得f最大,當然我們也需要記得01揹包迴圈的順序,所以需要根據s[i]的正負值分類考慮迴圈順序。詳見程式碼:

AC程式碼:

#include<iostream>
#include<cstdio>
using namespace std;

const int tab=1e5;
int s[105],f[105];
int dp[200005];

int main()
{
    int n,i,j;
    while(cin>>n)
    {
        for(i=0;i<n;i++)
            cin>>s[i]>>f[i];

        for(i=0;i<=2e5;i++)
            dp[i]=-1e8;
        dp[tab]=0;

        int l=0,r=0;
        for(i=0;i<n;i++)
        {
            l=min(l,l+s[i]);
            r=max(r,r+s[i]);
            if(s[i]<0)
            {
                for(j=l;j<=r;j++)
                {
                    dp[j+tab]=max(dp[j+tab-s[i]]+f[i],dp[j+tab]);
                }
            }
            else
            {
                for(j=r;j>=l;j--)
                {
                    dp[j+tab]=max(dp[j+tab-s[i]]+f[i],dp[j+tab]);
                }
            }
        }

        int res=0;
        for(i=0;i<=r;i++)
        {
            if(dp[i+tab]>=0)
                res=max(res,i+dp[i+tab]);
        }

        cout<<res<<endl;
    }
    return 0;
}

/*
5
-5 7
8 -6
6 -3
2 1
-8 -5
*/


相關文章