01揹包專題(DP問題)

01揹包的模板

小提醒:寫01揹包時要養成 初始化數組從1開始循環 的習慣

#無優化

for(int i=1;i<=n;i++)
{
    for(int c=1;c<=m;c++) //在這裏,揹包放入物品後,容量不斷的減少,直到再也放不進了
    {
        f[i][c]=f[i-1][c];
        if(c>=w[i])
        	f[i][c]=max(f[i][c],f[i-1][c-w[i]]+v[i]); // w爲體積 v爲價值
    }
}
// f[n][m]即爲最大值(以物品爲第一維數組)

#一維數組與判斷條件優化

for(int i=1;i<=n;i++)
{
    for(int c=m;c>=w[i];c--)
    {
        f[c]=max(f[c],f[c-w[i]]+v[i]); // w爲體積 v爲價值
    }
}
// f[m]即爲最大值(以容量爲第一維數組)

經典例題1

在這裏插入圖片描述
樣例輸入

2
50 1
5
10
1 2 3 2 1 1 2 3 2 1
50
0

樣例輸出

-45
32

AC代碼

#include <iostream>
#include <cstdio> 
#include <algorithm>
using namespace std;
int n;
int main()
{
    while(~scanf("%d",&n),n>0)
    {
        int i,price[1005]= {0},dp[1005] = {0}; //dp要初始化爲 0 
        for(i = 1; i<=n; i++)
            cin>>price[i];
        sort(price+1,price+1+n);
        int MAX=price[n];
        int j,m;
        cin>>m; //相當於揹包容量
        if(m<5) //低於5元不能購買
        {
            cout<<m<<endl;
            continue;
        }
        m-=5; //取出5元用於購買最貴的物品
        for(i = 1; i<n; i++)//01揹包
        {
            for(j = m;j>=price[i];j--)
            {
                dp[j] = max(dp[j],dp[j-price[i]]+price[i]);
            }
        }
        cout<<m+5-dp[m]-MAX<<endl;
    }
    return 0;
}

做法:首先要在現在所擁有的餘額中保留5元,用這五元去購買最貴的物品,而剩下的錢就是揹包的總容量,將容量減爲最小


經典例題2

在這裏插入圖片描述
樣例輸入

2 
10 1 
20 1 
3 
10 1 
20 2 
30 1 
-1

樣例輸出

20 10 
40 40

AC代碼

#include <iostream>
#include <cstdio> 
#include <cstring>
#include <algorithm>
using namespace std;
int val[5005]; //50 * 100 (不同設施的總數 * 設施的對應數量)
int dp[100000];
int main()
{
    int n,i,j,a,b,l,sum;
    while(~scanf("%d",&n),n>0)
    {
        memset(val,0,sizeof(val));
        memset(dp,0,sizeof(dp));
        l = 0;
        sum = 0;
        for(i = 0;i<n;i++)
        {
        	cin>>a>>b;
            while(b--)
            {
                val[l++] = a;
                sum+=a;
            }
        }
        for(i = 0;i<l;i++) //相當於物品個數 
        {
            for(j = sum/2;j>=val[i];j--) //sum/2 相當於揹包容量 
            {
                dp[j] = max(dp[j],dp[j-val[i]]+val[i]);
            }
        }
        cout<<sum-dp[sum/2]<<' '<<dp[sum/2]<<endl; //前者一定大於後者
    }
    return 0;
}

做法:換一種思維,因爲題目要求要儘量平均分配,所以我們可以先將總價值 sum 求出,然後得出其分配的平均值爲 sum/2,這個值即揹包總容量,可以得出最小那個價值(因爲01揹包算出的最大值小於等於揹包容量),所以另一個值即最大,用 sum 減去即可


經典例題3

在這裏插入圖片描述
樣例輸入

1
5 10
1 2 3 4 5
5 4 3 2 1

樣例輸出

14

AC代碼

#include<iostream>
using namespace std;
struct Node
{
	int val,vol;
}node[1005];
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		int n,v,sum=0,i,j;
		cin>>n>>v;
		int dp[1005]={0};
		for(int i=1;i<=n;i++)
			cin>>node[i].val;
		for(int i=1;i<=n;i++)
			cin>>node[i].vol;
		for(i=1;i<=n;i++)
			for(j=v;j>=node[i].vol;j--)
				dp[j]=max(dp[i],dp[j-node[i].vol]+node[i].val);
		cout<<dp[v]<<endl;
	}
	return 0;
}

01揹包的原型題,只是用一個結構體替換了兩個數組


經典例題4 (很難)


樣例輸入

3 
5 10 2 
1 2 3 4 5 
5 4 3 2 1 
5 10 12 
1 2 3 4 5 
5 4 3 2 1 
5 10 16 
1 2 3 4 5 
5 4 3 2 1

樣例輸出

12 
2 
0

AC代碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
 
struct Node
{
    int price;
    int val;
} node[1005];
 
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,v,k,i,dp[1005][31] = {0},a[31],b[31]; //dp[i][j]表示i體積下第j優解
        scanf("%d%d%d",&n,&v,&k); //骨頭的數量,袋子的體積,第 K優解 
        for(i = 0; i<n; i++)
            scanf("%d",&node[i].price);
        for(i = 0; i<n; i++)
            scanf("%d",&node[i].val);
        int j;
        for(i = 0; i<n; i++)
        {
            for(j = v; j>=node[i].val; j--)
            {
                int d;
                for(d = 1; d<=k; d++) //分別將放入第 i個石頭與不放第 i個石頭的結果存入數組 a b 中
                {
                    a[d] = dp[j-node[i].val][d]+node[i].price;
                    b[d] = dp[j][d];
                }
                int x,y,z;
                x = y = z = 1;
                a[d]=b[d]=-1; //此 d 相當於 k+1, 即越界
                while(a[x]!=-1||b[y]!=-1) //產生分歧後找到其中最大的 k的保留
                {
                    if(a[x] > b[y])
                        dp[j][z] = a[x++];
                    else
                        dp[j][z] = b[y++];
                    if(dp[j][z]!=dp[j][z-1]) //因爲優解的值不能相同, 去掉重複了的優解
                    	z++;
                }
            }
        }
        printf("%d\n",dp[v][k]);
    }
    return 0;
}

分析:因爲要求第k大,故不能有重複的解
做法:將放或不放某一塊石頭分別存入數組中,最後歸併從小到大排序進 dp 中,這樣就可以存儲特定體積下第 k 優解(最大價值),最後輸出即可


經典例題5(難)

在這裏插入圖片描述
樣例輸入

3 
0.04 3 
1 0.02 
2 0.03 
3 0.05 
0.06 3 
2 0.03 
2 0.03 
3 0.05 
0.10 3 
1 0.03 
2 0.02 
3 0.05

樣本輸出

2 
4 
6

AC代碼

#include<iostream>
#include<cstring>
using namespace std;
int money[110]; //表示每個銀行的錢(相當於揹包容量) 
double pp[110]; //被抓的概率 
double dp[110*110];
int main()
{
    int t;
    double p; //表示最大允許的概率
    int n; //計劃的銀行數
    cin>>t;
    while(t--)
    {
        memset(dp,0,sizeof(dp));
        int sum=0;
        cin>>p>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>money[i]>>pp[i];
            sum+=money[i];
        }
        dp[0]=1; //以概率 1 爲初始值 
        for(int i=1;i<=n;i++)
        {
            for(int j=sum;j>=money[i];j--)
            {
                dp[j]=max(dp[j],(dp[j-money[i]])*(1-pp[i]));
            }
        }
        int ans=0;
        for(int j=0;j<=sum;j++) //包括一個銀行都不能搶的情況 
        {
            if((1-dp[j]-p)<1e-8) //卡一點精度,由於是double類型
                ans=max(ans,j);
        }
        cout<<ans;
    }
    return 0; 
}

題目大意:先給出幾組數據,每組數據第一行是被抓的總概率 p (最後求得的總概率必須小於它,否則被抓),然後是想搶的銀行數 n 行,每行分別是該銀行能搶的錢數 m [ i ] 和被抓的概率 p [ i ],被抓的概率越大,逃跑概率越小,求最大逃跑概率
做法:揹包容量必然是錢數,然後是求最大逃跑概率,而題中每組數據第一行是被抓概率,所以要先用 1 減一下,還有最後求得的逃跑概率是隨着搶銀行的數量增加而減少,多搶一個銀行,其錢數必將轉化爲概率的乘積,所以動態方程也要做出改變;最後遍歷,剩餘的錢數越多,說明所搶的錢數越少,逃跑機率越大,所以從大到小遍歷揹包容量,一旦大於 p,即爲最大概率則跳出


經典例題6(難)

在這裏插入圖片描述
樣例輸入

2 10 
10 15 10 
5 10 5 
3 10 
5 10 5 
3 5 6 
2 7 3

樣例輸出

5 
11

AC代碼

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

struct node
{
    int p,q,v;
}a[505];

int cmp(node x,node y)//按 q-p排序,保證差額最小爲最優
{
    return x.q-x.p<y.q-y.p;
}

int main()
{
    int n,m,i,j;
    int dp[5005]={0};
    while(~scanf("%d%d",&n,&m))
    {
        for(i = 0; i<n; i++)
            cin>>a[i].p>>a[i].q>>a[i].v;
        memset(dp,0,sizeof(dp));
        sort(a,a+n,cmp);
        for(i = 0; i<n; i++)
        {
            for(j = m; j>=a[i].q; j--) //剩餘的錢大於 q 才能買
            {
                dp[j] = max(dp[j],dp[j-a[i].p]+a[i].v); //減去 p
            }
        }
        cout<<dp[m];
    }
    return 0;
}

分析dp[j] = max(dp[j],dp[j-a[i].p]+a[i].v) 表達的是這第 i 個物品要不要買,但同時也在判斷第 i 個物品要不要先買,如果先買剩下的空間隨便放,看是否得到的價值會更大
做法:將物品按差值(q - p)進行排序,小的排在前面,然後就用01揹包計算最高價值


經典例題7

在這裏插入圖片描述
樣例輸入

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

樣例輸出

123.50
1000.00
1200.50

AC代碼

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

int dp[3000005]; //由於每張發票不超過 1000,最多 30 張,擴大 100 倍數後開這麼大即可
 
int main()
{
    char ch;
    double x,y;
    int sum,a,b,c,money[35],v;
    int t,i,j,k;
    while(~scanf("%lf%d",&x,&t),t)
    {
        sum = (int)(x*100);//將小數化作整數處理
        memset(money,0,sizeof(money));
        memset(dp,0,sizeof(dp));
        int l = 0;
        for(i = 0; i<t; i++)
        {
            cin>>k;
            a = b = c = 0;
            int flag = 1;
            while(k--)
            {
                scanf(" %c:%lf",&ch,&y);
                v = (int)(y*100);
                if(ch == 'A' && a+v<=60000)
                    a+=v;
                else if(ch == 'B' && b+v<=60000)
                    b+=v;
                else if(ch == 'C' && c+v<=60000)
                    c+=v;
                else
                    flag = 0;
            }
            if(a+b+c<=100000 && a<=60000 && b<=60000 && c<=60000 && flag) //注意 flag
                money[l++] = a+b+c; //僅添加有效發票
        }
        for(i = 0; i<=l; i++)
        {
            for(j = sum; j>=money[i]; j--)
                    dp[j] = max(dp[j],dp[j-money[i]]+money[i]);
        }
        printf("%.2lf\n",dp[sum]/100.0);
    }
    return 0;
}

分析
1、只有 a,b,c 三種物品可以報銷,含有其他物品的發票作廢
2、每個物品的價值不能超過 600
3、每張發票總價值不能超過 1000
做法
先判斷哪些發票可以報銷,添加進 money 數組中,再對數組進行01揹包求出最大報銷金額


經典例題8(非常難)

題目描述
DD 和好朋友們要去爬山啦!他們一共有 K 個人,每個人都會背一個包,每個人包的容量是相同的,都是 V
可以裝進揹包裏的一共有 N 種物品,每種物品都有給定的體積和價值。
在 DD 看來,合理的揹包安排方案是這樣的:
1、每個人揹包裏裝的物品的總體積恰等於包的容量
2、每個包裏的每種物品最多隻有一件,但兩個不同的包中可以存在相同的物品
3、任意兩個人,他們包裏的物品清單不能完全相同
在滿足以上要求的前提下,所有包裏的所有物品的總價值最大是多少呢?
輸入格式
第一行有三個整數:K,V,N ( k<=50 v<=5000 n<=200)
第二行開始的 N 行,每行有兩個整數,分別代表這件物品的體積和價值
輸出格式
只需輸出一個整數,即在滿足以上要求的前提下所有物品的總價值的最大值
樣例輸入

2 10 5
3 12
7 20
2 4
5 6
1 1

樣例輸出

57

AC代碼

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

int n, v, k, c[205], w[205], q[55], ans = 0;
int f[5005][55]; //揹包容量 和 第 k優解 

int main()
{
    cin>>k>>v>>n; //人數 揹包容量 物品數
    memset(f, 128, sizeof(f)); f[0][1] = 0; //數組 f初始化越大越好且將f[0][1]初始化爲 0
    for(int i=1;i<=n;i++) cin >> c[i] >> w[i];
    for(int i=1;i<=n;i++)
        for(int j=v;j>=c[i];j--)
		{
            int now = 1, last = 1, cnt = 0;
            while(cnt < k) //不用去重(只要滿足不同物品即可) 
			{
                if(f[j][now] > f[j-c[i]][last]+w[i])
                    q[++cnt] = f[j][now++];
                else q[++cnt] = f[j-c[i]][last++]+w[i];
            }
            for(int z=1;z<=k;z++) f[j][z] = q[z];
        }
    for(int i=1;i<=k;i++) ans += f[v][i];
    cout<<ans;
    return 0;
}

分析:與經典例題4 類似,但是求前 k 個最優解,所以不用去重
做法:整體與經典例題4 類似,卻採用了數組 f 初始化越大越好 且 將f[0][1]初始化爲 0 的方法,沒有記錄放或不放物品的特解,而是直接將特解進行比較,從小到大存儲在新的數組中,再最後遍歷一遍將值重新賦回 (目前暫未掌握,有待理解)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章