01背包问题

[NOIP2005 普及组] 采药

题目描述

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?

输入格式

第一行有 \(2\) 个整数 \(T\)\(1 \le T \le 1000\))和 \(M\)\(1 \le M \le 100\)),用一个空格隔开,\(T\) 代表总共能够用来采药的时间,\(M\) 代表山洞里的草药的数目。

接下来的 \(M\) 行每行包括两个在 \(1\)\(100\) 之间(包括 \(1\)\(100\))的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式

输出在规定的时间内可以采到的草药的最大总价值。

样例输入

70 3
71 100
69 1
1 2

样例输出

3

提示

【数据范围】

  • 对于 \(30\%\) 的数据,\(M \le 10\)
  • 对于全部的数据,\(M \le 100\)

[!TIP]

01背包问题
特点:每个物品仅能使用一次
f[i][j]:表示所有选法集合中,只从前i个物品中选,并且总体积≤j的选法的集合,它的值是这个集合中每一个选法的最大值

状态转移方程
f[i][j] = max(f[i-1][j], f[i-1][j-v[i]]+w[i])
f[i-1][j]:不选第i个物品的集合中的最大值
f[i-1][j-v[i]]+w[i]:选第i个物品的集合,但是直接求不容易求所在集合的属性,先将第i个物品的体积减去,求剩下集合中选法的最大值

集合如何划分

一般原则:不重不漏,不重不一定都要满足(一般求个数时要满足)

如何将现有的集合划分为更小的子集,使得所有子集都可以计算出来

//无优化版
#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][N];
int main() {
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j <= m; j++) {
            f[i][j] = f[i-1][j];
            if(j>=v[i]) f[i][j] = max(f[i][j], f[i-1][j-v[i]]+w[i]);
        }
    }
    cout << f[n][m] << endl;
 return 0;    
}
//有优化版
//1. f[i] 仅用到了f[i-1]层
//2. j与j-v[i] 均小于j
//3.若用到上一层的状态时,从大到小枚举, 反之从小到大
#include <iostream>
using namespace std;
const int N = 1010;

int n, m;
int v[N], w[N];
int f[N];
int main() {
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
    
    for(int i = 1; i <= n; i++) 
        for(int j = m; j >= v[i]; j--) //01背包 一维写法 只能 逆序更新
      // 完全背包 一维写法 只能 正序更新:for (int j = v[i]; j <= v[i]; j ++)
            f[j] = max(f[j], f[j-v[i]]+w[i]);
    cout << f[m] << endl;
 return 0;    
}

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int T, M,f[N],t[N], m[N]; 
int main()
{
    scanf("%d %d", &T, &M); 
    for (int i = 1; i <= M; i++) {
        // 第i种草药的时间和价值
        scanf("%d %d", &t[i], &m[i]);
        for (int j = T; j >= t[i]; j--) {
            // 选择采摘该草药与不采摘该草药的最大价值
            f[j] = max(f[j], f[j - t[i]] + m[i]);
        }
    }
    printf("%d\n", f[T]); 

    return 0;  
}

搭配购买

题目描述

明天就是母亲节了,电脑组的小朋友们在忙碌的课业之余挖空心思想着该送什么礼物来表达自己的心意呢?听说在某个网站上有卖云朵的,小朋友们决定一同前往去看看这种神奇的商品,这个店里有 \(n\) 朵云,云朵已经被老板编号为 \(1,2,3,...,n\),并且每朵云都有一个价值,但是商店的老板是个很奇怪的人,他会告诉你一些云朵要搭配起来买才卖,也就是说买一朵云则与这朵云有搭配的云都要买,电脑组的你觉得这礼物实在是太新奇了,但是你的钱是有限的,所以你肯定是想用现有的钱买到尽量多价值的云。

输入格式

第一行输入三个整数,\(n,m,w\),表示有 \(n\) 朵云,\(m\) 个搭配和你现有的钱的数目。

第二行至 \(n+1\) 行,每行有两个整数, \(c_i,d_i\),表示第 \(i\) 朵云的价钱和价值。

\(n+2\)\(n+1+m\) 行 ,每行有两个整数 \(u_i,v_i\)。表示买第 \(u_i\) 朵云就必须买第 \(v_i\) 朵云,同理,如果买第 \(v_i\) 朵就必须买第 \(u_i\) 朵。

输出格式

一行,表示可以获得的最大价值。

样例输入

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

样例输出

1

提示

  • 对于 \(30\%\) 的数据,满足 \(1 \le n \le 100\)
  • 对于 \(50\%\) 的数据,满足 \(1 \le n, w \le 10^3\)\(1 \le m \le 100\)
  • 对于 \(100\%\) 的数据,满足 \(1 \le n, w \le 10^4\)\(0 \le m \le 5 \times 10^3\)

[!TIP]

并查集将每一组搭配的云朵合并再看作01背包问题

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 5;
int a, b, n, m, w, c[N], d[N], f[N] , dp[N], baka=0; 

int find(int x) {//查找(并查集)
    if (f[x] == x) {
	return x; 
    }else{
    return f[x] = find(f[x]); 
    }
}

int main() 
{
    scanf("%d %d %d", &n, &m, &w);
    for (int i = 1; i <= n; i++) 
	{
        scanf("%d %d", &c[i], &d[i]);
        f[i] = i; //初始化
    }
    for (int i = 1; i <= m; i++) //搭配
	{
        scanf("%d %d", &a, &b);
        int x = find(a), y = find(b);

        if (x != y) { 
            c[y] += c[x]; d[y] += d[x];//合并
            c[x] = d[x] = 0; //清空
            f[x] = y;
        }
    }
    for (int i = 1; i <= n; i++) {//预算w能获得的最大价值
            for (int j = w; j >= c[i]; j--) { 
                dp[j] = max(dp[j], dp[j - c[i]] + d[i]);
            }
    }
    for (int i = 1; i <= w; i++) {
        baka = max(baka, dp[i]); 
    }
    printf("%d\n", baka);
    return 0;
}



posted @ 2024-11-27 02:06  土木牢盖  阅读(34)  评论(0)    收藏  举报