The Festival of Insi|

hsy2093

园龄:1年5个月粉丝:0关注:0

背包问题(多重背包与分组背包)

多重背包问题

与01背包的区别在于每个物品的个数有限制,且不一样。

f[i, j] = max(f[i-1,j-v[i]*k] + w[i]*k, k为选择放进背包里的当前物品的个数)

优化过程

对比两个状态转移方程

//其中s代表对于第i个物品而言限制的最大数量
f[i,j] = max(f[i-1, j], f[i-1, j-v]+w, f[i-2, j-2v]+ 2w,...,f[i-1, j-sv]+sw)
f[i, j-v] = max(f[i-1, j-v], f[i-1, j-2v]+w, f[i-1, j-3v]+2w,...,f[i-1,j-sv]+(s-1)w, f[i-1,j-(s+1)v]+sw)

发现f[i,jv]相比于f[i,j]f[i1,j(s+1)v+sw]

因此无法利用完全背包的思维进行优化。

在多重背包中,常用的优化方法为二进制优化

例:假设当前物品最多只能选1023件,可以将其拆分成:

数量为1, 2, 4, 8, ... 512的物品,每件物品最多只能选一次。

这样就能表示出来选取数量的所有方案

当s = 200时,应拆分为1, 2, 4, 8, 16,32, 64, 73(注意73)

代码做法:

先对所有物品,按照限制的数量s[i]进行拆分,可按照二进制优化的方法拆分成logs[i]个物品

再对所有物品拆分完之后的物品进行01背包的做法即可。

这样可以把枚举数量的第三层循环从O(s)缩小到O(logs)

分组背包问题

所有物品被分为几组,每一组的数量有限制,问最大的价值为多少。

状态转移方程

//k为在该组内限制数量的范围枚举
f[i, j] = max(f[i-1, j-v[i,k]+w[i,k]])

习题

1. 通天之分组背包

题目背景

直达通天路·小 A 历险记第二篇

题目描述

01 背包问世之后,小 A 对此深感兴趣。一天,小 A 去远游,却发现他的背包不同于 01 背包,他的物品大致可分为 k 组,每组中的物品相互冲突,现在,他想知道最大的利用价值是多少。

输入格式

两个数 m,n,表示一共有 n 件物品,总重量为 m

接下来 n 行,每行 3 个数 ai,bi,ci,表示物品的重量,利用价值,所属组数。

输出格式

一个数,最大的利用价值。

样例 #1

样例输入 #1

45 3
10 10 1
10 5 1
50 400 2

样例输出 #1

10

提示

0m10001n10001k100ai,bi,ciint 范围内。

参考代码

//P1757
//分组背包
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int f[105][1005];

struct wp{
    int a, b, c, d;
}w[1005];

bool cmp(wp x, wp y){
    return x.c < y.c;
}

int main(){
    int m, n;
    cin >> m >> n;
    for(int i = 1; i <= n; i++)     cin >> w[i].a >> w[i].b >> w[i].c;
    sort(w+1, w+n+1, cmp);
    int num = 1;
    w[1].d = num;
    for(int i = 2; i <= n; i++){
        if(w[i].c != w[i-1].c)  num ++;
        w[i].d = num;
    }
    for(int i = 1; i <= n; i++){
        int idx = w[i].d;
        for(int j = 0; j <= m; j++){
            f[idx][j] = max(f[idx][j], f[idx-1][j]);
            if(j >= w[i].a)       f[idx][j] = max(f[idx][j], f[idx-1][j-w[i].a] + w[i].b);
        }
    }
    cout << f[w[n].c][m] << endl;
    return 0;
}

2. 搭配购买

题目描述

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

输入格式

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

第二行至 n+1 行,每行有两个整数, ci,di,表示第 i 朵云的价钱和价值。

n+2n+1+m 行 ,每行有两个整数 ui,vi。表示买第 ui 朵云就必须买第 vi 朵云,同理,如果买第 vi 朵就必须买第 ui 朵。

输出格式

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

样例 #1

样例输入 #1

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

样例输出 #1

1

提示

  • 对于 30% 的数据,满足 1n100
  • 对于 50% 的数据,满足 1n,w1031m100
  • 对于 100% 的数据,满足 1n,w1040m5×103

参考代码

//P1455
#include<stdio.h>
#include<iostream>
using namespace std;
int fa[10005], buy[10005], val[10005];
int f[10005];
bool flag[10005];
int find(int x){
    if(x == fa[x])      return x;
    return fa[x] = find(fa[x]);
}

void merge(int x, int y){
    buy[find(y)] += buy[find(x)];
    val[find(y)] += val[find(x)];
    fa[find(x)] = find(y);
}

int main(){
    int n, m, w;
    int u, v;
    cin >> n >> m >> w;
    for(int i = 1; i <= n; i++){
        cin >> buy[i] >> val[i];
        fa[i] = i;
    }
    for(int i = 1; i <= m; i++){
        cin >> u >> v;
        if(find(u) != find(v))      merge(u, v);
    }
    for(int i = 1; i <= n; i++){
        int ff = find(i);
        if(!flag[ff]){
            for(int j = w; j >= buy[ff]; j--){
                f[j] = max(f[j], f[j-buy[ff]] + val[ff]);
            }
            flag[ff] = 1;
        }
    }
    cout << f[w] << endl;
    return 0;
}

3. [USACO2.2] 集合 Subset Sums

题目描述

对于从 1n 的连续整数集合,能划分成两个子集合,且保证每个集合的数字和是相等的。举个例子,如果 n=3,对于 {1,2,3} 能划分成两个子集合,每个子集合的所有数字和是相等的:

{3}{1,2} 是唯一一种分法(交换集合位置被认为是同一种划分方案,因此不会增加划分方案总数)
如果 n=7,有四种方法能划分集合 {1,2,3,4,5,6,7},每一种分法的子集合各数字和是相等的:

{1,6,7}{2,3,4,5}
{2,5,7}{1,3,4,6}
{3,4,7}{1,2,5,6}
{1,2,4,7}{3,5,6}

给出 n,你的程序应该输出划分方案总数。

输入格式

输入文件只有一行,且只有一个整数 n

输出格式

输出划分方案总数。

样例 #1

样例输入 #1

7

样例输出 #1

4

提示

【数据范围】
对于 100% 的数据,1n39

翻译来自NOCOW

USACO 2.2

参考代码

//P1466
//题意转换为
//1~n中选取k个数,其和为(1+n)*n/4的情况总数
#include<stdio.h>
#include<iostream>
using namespace std;
typedef long long ll;
ll f[50][2500];

int main(){
    int n, sum;
    cin >> n;
    if((1 + n) * n / 2 % 2 == 1){
        cout << 0 << endl;
        return 0;
    }
    sum = (1 + n) * n / 4;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= sum; j++){
            if(j <= i){
                f[i][j] = max(f[i][j], f[i-1][j]);
                if(j == i)  f[i][j] ++;
            }
            else    f[i][j] = f[i-1][j-i] + f[i-1][j];
        }
    }
    cout << f[n][sum] / 2 << endl;
    return 0;
}

4. 宝物筛选

题目描述

终于,破解了千年的难题。小 FF 找到了王室的宝物室,里面堆满了无数价值连城的宝物。

这下小 FF 可发财了,嘎嘎。但是这里的宝物实在是太多了,小 FF 的采集车似乎装不下那么多宝物。看来小 FF 只能含泪舍弃其中的一部分宝物了。

小 FF 对洞穴里的宝物进行了整理,他发现每样宝物都有一件或者多件。他粗略估算了下每样宝物的价值,之后开始了宝物筛选工作:小 FF 有一个最大载重为 W 的采集车,洞穴里总共有 n 种宝物,每种宝物的价值为 vi,重量为 wi,每种宝物有 mi 件。小 FF 希望在采集车不超载的前提下,选择一些宝物装进采集车,使得它们的价值和最大。

输入格式

第一行为一个整数 nW,分别表示宝物种数和采集车的最大载重。

接下来 n 行每行三个整数 vi,wi,mi

输出格式

输出仅一个整数,表示在采集车不超载的情况下收集的宝物的最大价值。

样例 #1

样例输入 #1

4 20
3 9 3
5 9 1
9 4 2
8 1 3

样例输出 #1

47

提示

对于 30% 的数据,nmi1040W103

对于 100% 的数据,nmi1050W4×1041n100

#include<iostream>
using namespace std;
struct wp{
	int v, w, m;
}p[100005], er[1000];
int f[400005];
 
int main(){
	int n, w;
	int idx = 0;
	cin >> n >> w;
	for(int i = 1; i <= n; i++)		cin >> p[i].v >> p[i].w >> p[i].m;
	for(int i = 1; i <= n; i++){
		//将数量进行二进制处理
		idx ++; 
		er[idx].w = p[i].w;
		er[idx].v = p[i].v;
		int temp = 1,sum = 1;
		while(sum + temp * 2 <= p[i].m){
			idx ++;
			er[idx].w = er[idx-1].w*2;
			er[idx].v = er[idx-1].v*2;
			temp *= 2;
			sum += temp;
		}
		int cha = p[i].m - sum;
		if(cha > 0){
			idx ++;
			er[idx].w = p[i].w * cha;
			er[idx].v = p[i].v * cha;
		}
	}
	//转换成01背包问题
	for(int i = 1; i <= idx; i++){
		for(int j = w; j >= 0; j--){
			if(j >= er[i].w)	f[j] = max(f[j], f[j-er[i].w] + er[i].v);
		}
	}
	cout << f[w] << endl;
}

本文作者:hsy2093

本文链接:https://www.cnblogs.com/hsy2093/p/18240035

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   hsy2093  阅读(62)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起