01背包入门题集

整理一下我之前做过的01背包的基础题目,以后不断更新

PS:限于篇幅,每道题目只给出粗略分析以及核心代码

 

1. POJ 3624 Charm Bracelet

  这题就是赤裸裸的01背包裸题,不过开二维的数组似乎不行,那么一维能节省空间。背包的第二个for循环是倒序的,这是因为dp[i][j]是由dp[i-1][j-v[i]]转移来的。如果顺序的话,是从dp[i][j-v[i]]转移来的,那是完全背包的状态转移方程。

  代码:

memset (dp, 0, sizeof (dp));
    for (int i=1; i<=n; ++i)    {
        for (int j=m; j>=v[i]; --j) {
            if (dp[j] < dp[j-v[i]] + w[i])  {
                dp[j] = dp[j-v[i]] + w[i];
            }
        }
    }
printf ("%d
", dp[m]);

2. POJ 3628 Bookshelf 2

  这题比上一题绕了个弯,求超过b的最小高度。先定背包容量为总高度sum,dp[j] 表示当前容量为j时的最大高度为多少。然后从b开始枚举,当dp[j] >= b时,那么dp[j] - b一定是最小高度差

  代码:

int sum = 0;
for (int i=1; i<=n; ++i)    {
    scanf ("%d", &h[i]);
    sum += h[i];
}
for (int i=0; i<=sum; ++i)  dp[i] = 0;
for (int i=1; i<=n; ++i)    {
    for (int j=sum; j>=h[i]; --j)   {
        if (dp[j] < dp[j-h[i]] + h[i])  {
            dp[j] = dp[j-h[i]] + h[i];
        }
    }
}
int ans = INF;
for (int i=b; i<=sum; ++i)  {
    if (dp[i] >= b)  {
        ans = dp[i] - b;    break;
    }
}
printf ("%d
", ans);

  

3. HDOJ 2546 饭卡

  首先将m - 5,不考虑最贵的菜(剩下的钱+5可以买下最贵的菜),那么前n-1道菜用01背包,dp[j] 表示用j余额最大的消费,最后处理最贵的菜。

  代码:

if (m < 5)  {
	printf ("%d
", m);
}
else    {
	sort (price+1, price+n+1);
	int mx = price[n];
	m -= 5;
	memset (dp, 0, sizeof(dp));
	for (int i=1; i<=n-1; ++i)  {
		for (int j=m; j>=price[i]; --j) {
			dp[j] = max (dp[j], dp[j-price[i]] + price[i]);
		}
	}
	printf ("%d
", m + 5 - dp[m] - mx);
}

  

4. POJ 1948 Triangular Pastures

  首先我们考虑给出3边求面积,用到海伦公式P = (a + b + c) / 2.0; S = sqrt(p * (p - a) * (p - b) * (p - c))。因为每条木板最多只能用到一次,用01背包写~ 二维dp:dp[i][j] = true 表示边i和边j能构成一个三角形,那么第三边就是sum - i - j。其次,在一个三角形中,每条边最大是sum / 2;

  代码:

int sum = 0;
for (int i=1; i<=n; ++i)    {
    scanf ("%d", &l[i]);    sum += l[i];
}
int half = sum / 2;
memset (dp, false, sizeof (dp));
dp[0][0] = true;
for (int i=1; i<=n; ++i)    {
    for (int j=half; j>=0; --j) {
        for (int k=j; k>=0; --k)    {
            if ((j >= l[i] && dp[j-l[i]][k]) || k >= l[i] && dp[j][k-l[i]]) {
                dp[j][k] = true;
            }
        }
    }
}
int ans = -1;
for (int i=half; i>=1; --i) {
    for (int j=i; j>=1; --j)    {
        int k = sum - i - j;
        if (dp[i][j] && ok (i, j, k))   {
            ans = max (ans, area (i, j, k));
        }
    }
}
printf ("%d
", ans);

  


5. POJ 1837 Balance
  设f[i][j]代表挂了前i个砝码时,平衡度为j的方法的个数。显然,平衡度的范围为[-25*20*15,25*20*15],即[-7500,7500],但是要使天平保持平衡,一旦平衡度超过3750,或小于-3750,那么剩下的砝码无论如何放,天平都无法平衡,所以这种情况可以不用考虑。当然,实际时,由于不允许有负数,所以f[i][j]需要进行必要的修改。

  代码:

memset (dp, 0, sizeof (dp));
dp[0][3750] = 1;                                    //修改成3750为平衡点 
for (int i=1; i<=m; ++i)    {
    for (int j=0; j<=15000; ++j)    {
        if (dp[i-1][j]) {                           //从3750开始
            for (int k=1; k<=n; ++k)    {           //可能下垂任何钩的重量,但他是被迫使用所有的重量
                dp[i][j+l[k]*w[i]] += dp[i-1][j];   //dp[i][j] 代表挂了前i个砝码时,平衡度为j的方法的个数
            }
        }
    }
}
printf ("%d
", dp[m][3750]);                       //输出平衡点的个数

6. POJ 2184 Cow Exhibition

  给出num(num<=100)头奶牛的S和F值(-1000<=S,F<=1000),要求在这几头奶牛中选出若干头,使得在其总S值TS和总F值TF均不为负的前提下,求最大的TS+TF值,可以把S当体积,F当价值做01背包。但是注意是S可为负,所以整体加100000,然后要注意DP顺序,S为负是要顺序,为正时逆序。如果上一题会的话,这一题也不难。

  代码:

for (int i=0; i<=200000; ++i)   dp[i] = -INF;
dp[100000] = 0;
for (int i=1; i<=n; ++i)
{
    if (v[i] > 0)   {
        for (int j=200000; j>=v[i]; --j)    {
            if (dp[j-v[i]] > -INF)
                dp[j] = max (dp[j], dp[j-v[i]] + w[i]);
        }
    }
    else    {
        for (int j=0; j=200000+v[i]; ++j)   {
            if (dp[j-v[i]] > -INF)
                dp[j] = max (dp[j], dp[j-v[i]] + w[i]);
        }
    }
}
int ans = 0;
for (int i=100000; i<=200000; ++i)  {
    if (dp[i] >= 0 && dp[i]+i-100000 > ans)
        ans = dp[i] + i - 100000;
}
printf ("%d
", ans);

7. HDOJ 1203 I NEED A OFFER!

  概率问题,考虑对立事件,P (拿到offer) = 1 - P (上一次没拿到offer) * P (这次没拿到offer)

  代码:

memset (dp, 0, sizeof (dp));
for (int i=1; i<=m; ++i)    {
	for (int j=n; j>=v[i]; --j) {
		dp[j] = max (dp[j], 1 - (1 - dp[j-v[i]]) * (1 - p[i]));
	}
}
printf ("%.1lf%%
", dp[n] * 100);

  

8. HDOJ 2955 Robberies

  概率问题,与上一题类似,反向思维

  代码:

int sum = 0;
for (int i=1; i<=N; ++i)    {
    scanf ("%d%f", &m[i], &p[i]);
    sum += m[i];						                    //反向思维, 考虑逃脱概率
}
for (int i=1; i<=sum; ++i)  dp[i] = 0;
dp[0] = 1;								                    //没偷钱逃脱的概率
for (int i=1; i<=N; ++i)    {
    for (int j=sum; j>=m[i]; --j)	{                       //偷钱逃脱的概率
        dp[j] = max (dp[j], dp[j-m[i]] * (1 - p[i])); 		//少写了个d ->成p (/ □ )
    }
}
P = 1 - P;
for (int i=sum; i>=0; --i)  {
    if (dp[i] >= P)		{                                   //寻找大于成功逃脱概率的最大偷钱数
        printf ("%d
", i);
        break;
    }
}

  

编译人生,运行世界!
原文地址:https://www.cnblogs.com/Running-Time/p/4791945.html