背包问题大全

目录

〇,总结

背景

模板

一,0-1背包

0-1背包

HDU 2602 Bone Collector

力扣 416. 分割等和子集

力扣 1049. 最后一块石头的重量 II

力扣 2742. 给墙壁刷油漆

​CSU 1197 Staginner 买葡萄

CSU 1945 最简单的题目

爱奇艺节目采购

0-1增强代价背包

HDU 3466 Proud Merchants

0-1双得分背包

UVA 12563 Jin Ge Jin Qu hao(正得分)

01背包组合计数

力扣 494. 目标和

二,完全背包

完全背包

完全增强代价背包

完全双得分背包

力扣 322. 零钱兑换(负得分)

完全背包组合计数

力扣 518. 零钱兑换 II

完全背包排列计数

力扣 377. 组合总和 Ⅳ

力扣 70. 爬楼梯

三,多重背包

多重背包

HDU - 2191 悼念512汶川大地震遇难同胞——珍惜现在,感恩生活

四,混合背包

混合背包

洛谷 - U115689 混合背包

五,双代价背包

0-1双代价背包

力扣 474. 一和零

强双代价背包

多重双代价背包

混合双代价背包

六,分组背包

分组01背包

HDU - 1712 ACboy needs your help

分组01背包组合计数

七,泛化物品背包

泛化物品背包

八,依赖背包

依赖背包

 CSU 1793 Outing 

九,泛背包问题(贪心)


〇,总结

背景

背包系列的问题非常灵活多变,理解了简单的问题之后,复杂问题往往是组合拼装,本文列举了常见的组合,还有一些组合没写。

以一到六章为例:

问题类型分为,求值,组合计数,排列计数,所以是3种,
背包类型分为,分组背包,非分组背包,所以是2种,
分组背包,每组挑选1个物品,变成非分组背包,
非分组背包,按代价分为单代价,单增强代价,双代价,按得分分为单得分,双得分,按背包分为01背包,完全背包,多重背包,组合背包,所以是3*2*4=24种,
一共是3*2*24=144种问题。

可能有少部分组合无意义,应该还有100个左右是有意义的。

模板

一共有如下几个类,除了单调队列之外,和本文所有章节(除掉总结和泛背包问题)可以对应上。

MonotonicQueue   单调队列

Pack       01背包、完全背包、多重背包、混合背包

PackDoubleVolume    双代价背包 

PackGroup   分组背包

PackGeneralize   泛化物品背包

PackDepend   依赖背包

除非专门声明,否则所有输入都是非负数

//单调队列
class MonotonicQueue {
public:
	MonotonicQueue(int type) { //0递增队列,队首最小,1递减队列,队首最大
		this->type = type;
		id = 0;
	}
	void push_back(int x) {
		while (!q.empty() && (type ? (m[q.back()] < x) : (m[q.back()] > x)))q.pop_back();
		q.push_back(id);
		m[id++] = x;
	}
	void pop_front() {
		if (!q.empty())q.pop_front();
	}
	void pop_back() {
		if (!q.empty())q.pop_back();
	}
	int frontId() {
		return q.front();
	}
	int front() {
		return m[q.front()];
	}
	int tailId() {
		return q.back();
	}
	int size() {
		return q.size();
	}
private:
	deque<int>q;
	map<int, int>m;
	int type, id;
};

//01背包、完全背包、多重背包、混合背包
class Pack {
public:
	//01背包,输入总体积,每个物品的体积和得分,输出最高总得分
	static int pack01(int v, const vector<int>& volume, const vector<int>& score) {
		vector<int>ans(v + 1, 0);
		for (int i = 0; i < volume.size(); i++)pack01Opt(v, volume[i], score[i], ans);
		return ans[v];
	}
	//0-1增强代价背包,需要有volumePlus大小的空间,才能放入volume大小的物品,输出最高总得分
	static int packPlus(int v, const vector<int>& volumePlus, const vector<int>& volume, const vector<int>& score) {
		vector<node>vec = transPackPlusToNodes(volumePlus, volume, score);
		vector<int>ans(v + 1, 0);
		for (int i = 0; i < vec.size(); i++) {
			for (int j = v; j >= vec[i].vp; j--) {
				ans[j] = max(ans[j], ans[j - vec[i].v] + vec[i].s);
			}
		}
		return ans[v];
	}
	//0-1双得分背包,每个物品有2个得分,输出score的最高总得分,以及在该得分前提下,score2的最高总得分,score2可以为负
	static vector<int> packDoubleScore(int v, const vector<int>& volume, const vector<int>& score, const vector<int>& score2) {
		vector<int>ans(v + 1, 0), ans2(v + 1, 0);
		for (int i = 0; i < volume.size(); i++) {
			for (int j = v; j >= volume[i]; j--) {
				int j2 = j - volume[i];
				if (ans[j] < ans[j2] + score[i])ans[j] = ans[j2] + score[i], ans2[j] = ans2[j2] + score2[i];
				else if (ans[j] == ans[j2] + score[i] && ans2[j] < ans2[j2] + score2[i])ans2[j] = ans2[j2] + score2[i];
			}
		}
		return vector<int>{ans[v], ans2[v]};
	}
	//01背包组合计数,输入总体积,每个物品的体积,输出刚好塞满的方案数,不区分顺序
	static int pack01Num(int v, const vector<int>& volume) {
		vector<int>ans(v + 1, 0);
		ans[0] = 1;
		for (int i = 0; i < volume.size(); i++) {
			for (int j = v; j >= volume[i]; j--) {
				ans[j] += ans[j - volume[i]];
			}
		}
		return ans[v];
	}
	//完全背包,输入总体积,每个物品的体积和得分,输出最高总得分
	static int packComplete(int v, const vector<int>& volume, const vector<int>& score) {
		vector<int>ans(v + 1, 0);
		for (int i = 0; i < volume.size(); i++)pack01Opt(v, volume[i], score[i], ans);
		return ans[v];
	}
	//完全增强代价背包,需要有volumePlus大小的空间,才能放入volume大小的物品,输出最高总得分
	static int packCompletePlus(int v, const vector<int>& volumePlus, const vector<int>& volume, const vector<int>& score) {
		vector<node>vec = transPackPlusToNodes(volumePlus, volume, score);
		vector<int>ans(v + 1, 0);
		for (int i = 0; i < vec.size(); i++) {
			for (int j = vec[i].vp; j <= v; j++) {
				ans[j] = max(ans[j], ans[j - vec[i].v] + vec[i].s);
			}
		}
		return ans[v];
	}
	//完全双得分背包,每个物品有2个得分,输出score的最高总得分,以及在该得分前提下,score2的最高总得分,score2可以为负
	static vector<int> packCompleteDoubleScore(int v, const vector<int>& volume, const vector<int>& score, const vector<int>& score2) {
		vector<int>ans(v + 1, 0), ans2(v + 1, 0);
		for (int i = 0; i < volume.size(); i++) {
			for (int j = volume[i]; j <= v; j++) {
				int j2 = j - volume[i];
				if (ans[j] < ans[j2] + score[i])ans[j] = ans[j2] + score[i], ans2[j] = ans2[j2] + score2[i];
				else if (ans[j] == ans[j2] + score[i] && ans2[j] < ans2[j2] + score2[i])ans2[j] = ans2[j2] + score2[i];
			}
		}
		return vector<int>{ans[v], ans2[v]};
	}
	//完全背包组合计数,输入总体积,每个物品的体积,输出刚好塞满的方案数,不区分顺序
	static int packCompleteNum(int v, const vector<int>& volume) {
		vector<int>ans(v + 1, 0);
		ans[0] = 1;
		for (int i = 0; i < volume.size(); i++) {
			for (int j = volume[i]; j <= v; j++) {
				ans[j] += ans[j - volume[i]];
			}
		}
		return ans[v];
	}
	//完全背包排列计数,输入总体积,每个物品的体积,输出刚好塞满的方案数,区分顺序
	static long long packCompleteNumA(int v, const vector<int>& volume, long long p = UINT_MAX) {
		vector<long long>ans(v + 1, 0);
		ans[0] = 1;
		for (int j = 0; j <= v; j++) {
			for (int i = 0; i < volume.size(); i++) {
				if (j >= volume[i])ans[j] = (ans[j] + ans[j - volume[i]]) % p;
			}
		}
		return ans[v];
	}
	//多重背包,输入总体积,每个物品的体积和数量和得分,输出最高总得分
	static int packMulti(int v, const vector<int>& volume, const vector<int>& num, const vector<int>& score) {
		vector<int>ans(v + 1, 0);
		for (int i = 0; i < volume.size(); i++)packMultiOpt(v, volume[i], num[i], score[i], ans);
		return ans[v];
	}
	//01、完全、多重的混合背包,num=1是01背包,num=-1是完全背包,num>1是多重背包
	static int packMix(int v, const vector<int>& volume, const vector<int>& num, const vector<int>& score) {
		vector<int>ans(v + 1, 0);
		for (int i = 0; i < volume.size(); i++) {
			if (num[i] == 1)pack01Opt(v, volume[i], score[i], ans);
			if (num[i] == -1)packCompleteOpt(v, volume[i], score[i], ans);
			if (num[i] > 1)packMultiOpt(v, volume[i], num[i], score[i], ans);
		}
		return ans[v];
	}
private:
	struct node
	{
		int vp, v, s;
		static bool cmp(node a, node b)
		{
			return a.vp - a.v < b.vp - b.v;
		}
	};
	static vector<node> transPackPlusToNodes(const vector<int>& volumePlus, const vector<int>& volume, const vector<int>& score)
	{
		vector<node>vec;
		for (int i = 0; i < volumePlus.size(); i++) {
			vec.push_back({ volumePlus[i],volume[i],score[i] });
		}
		sort(vec.begin(), vec.end(), node::cmp);
		return vec;
	}
	static inline void pack01Opt(int v, int volumei, int scorei, vector<int>&ans) {
		for (int j = v; j >= volumei; j--) {
			ans[j] = max(ans[j], ans[j - volumei] + scorei);
		}
	}
	static inline void packCompleteOpt(int v, int volumei, int scorei, vector<int>&ans) {
		for (int j = volumei; j <= v; j++) {
			ans[j] = max(ans[j], ans[j - volumei] + scorei);
		}
	}
	static inline void packMultiOpt(int v, int volumei, int numi, int scorei, vector<int>&ans) {
		for (int j = 0; j < volumei; j++) {
			MonotonicQueue q(1);
			for (int k = j; k <= v; k += volumei) {
				q.push_back(ans[k] - k / volumei * scorei);
				ans[k] = k / volumei * scorei + q.front();
				if (q.tailId() - numi == q.frontId())q.pop_front();
			}
		}
	}
};

//双代价背包
class PackDoubleVolume {
public:
	//0-1双代价背包,每个物品有2个代价,输出最高总得分
	static int pack01(int v, int v2, const vector<int>& volume, const vector<int>& volume2, const vector<int>& score) {
		vector<vector<int>>ans(v + 1, vector<int>(v2 + 1, 0));
		for (int i = 0; i < volume.size(); i++)pack01Opt(v, v2, volume[i], volume2[i], score[i], ans);
		return ans[v][v2];
	}
	//增强双代价背包,每个物品有2个代价,输出最高总得分
	static int packComplete(int v, int v2, const vector<int>& volume, const vector<int>& volume2, const vector<int>& score) {
		vector<vector<int>>ans(v + 1, vector<int>(v2 + 1, 0));
		for (int i = 0; i < volume.size(); i++)packCompleteOpt(v, v2, volume[i], volume2[i], score[i], ans);
		return ans[v][v2];
	}
	//多重双代价背包,每个物品有2个代价,输出最高总得分
	static int packMulti(int v, int v2, const vector<int>& volume, const vector<int>& volume2, const vector<int>& num, const vector<int>& score) {
		vector<vector<int>>ans(v + 1, vector<int>(v2 + 1, 0));
		for (int i = 0; i < volume.size(); i++) packMultiOpt(v, v2, volume[i], volume2[i], num[i], score[i], ans);
		return ans[v][v2];
	}
	//01、完全、多重的混合背包,num=1是01背包,num=-1是完全背包,num>1是多重背包
	static int packMix(int v, int v2, const vector<int>& volume, const vector<int>& volume2, const vector<int>& num, const vector<int>& score) {
		vector<vector<int>>ans(v + 1, vector<int>(v2 + 1, 0));
		for (int i = 0; i < volume.size(); i++) {
			if (num[i] == 1)pack01Opt(v, v2, volume[i], volume2[i], score[i], ans);
			if (num[i] == -1)packCompleteOpt(v, v2, volume[i], volume2[i], score[i], ans);
			if (num[i] > 1)packMultiOpt(v, v2, volume[i], volume2[i], num[i], score[i], ans);
		}
		return ans[v][v2];
	}
private:
	static inline void pack01Opt(int v, int v2, int volumei, int volume2i, int scorei, vector<vector<int>>&ans) {
		for (int j = v; j >= volumei; j--) {
			for (int j2 = v2; j2 >= volume2i; j2--) {
				ans[j][j2] = max(ans[j][j2], ans[j - volumei][j2 - volume2i] + scorei);
			}
		}
	}
	static inline void packCompleteOpt(int v,int v2, int volumei,int volume2i, int scorei, vector<vector<int>>&ans) {
		for (int j = volumei; j <= v; j++) {
			for (int j2 = volume2i; j2 <= v2; j2++) {
				ans[j][j2] = max(ans[j][j2], ans[j - volumei][j2 - volume2i] + scorei);
			}
		}
	}
	static inline void packMultiOpt(int v, int v2, int volumei, int volume2i, int numi, int scorei, vector<vector<int>>&ans) {
		for (int j = 0; j <= v; j++) {
			for (int j2 = 0; j2 <= (j < volumei ? v2 : volume2i - 1); j2++) {
				MonotonicQueue q(1);
				for (int k = j, k2 = j2; k <= v, k2 <= v2; k += volumei, k2 += volume2i) {
					q.push_back(ans[k][k2] - k / volumei * scorei);
					ans[k][k2] = k / volumei * scorei + q.front();
					if (k / volumei - numi == q.frontId())q.pop_front();
				}
			}
		}
	}
};
//分组背包
class PackGroup {
public:
	//分组背包,输入总体积,每组每个物品的体积和得分,输出最高总得分
	static int packGroup01(int v, const vector<vector<int>>& volume, const vector<vector<int>>& score) {
		return packGroup01Ans(v, volume, score)[v];
	}
	//分组背包组合计数,输入总体积,每组每个物品的体积,输出刚好塞满的方案数,不区分顺序
	static int packGroup01Num(int v, const vector<vector<int>>& volume) {
		vector<int>ans(v + 1, 0);
		for (int i = 0; i < volume.size(); i++) {
			for (int j = v; j >= 0; j--) {
				for (int k = 0; k < volume[i].size(); k++) {
					if (j >= volume[i][k])ans[j] += ans[j - volume[i][k]];
				}
			}
		}
		return ans[v];
	}
protected:
	//分组背包,输入总体积,每组每个物品的体积和得分,输出最高总得分
	static vector<int> packGroup01Ans(int v, const vector<vector<int>>& volume, const vector<vector<int>>& score) {
		vector<int>ans(v + 1, 0);
		for (int i = 0; i < volume.size(); i++) {
			for (int j = v; j >= 0; j--) {
				for (int k = 0; k < volume[i].size(); k++) {
					if (j >= volume[i][k])ans[j] = max(ans[j], ans[j - volume[i][k]] + score[i][k]);
				}
			}
		}
		return ans;
	}
};
//泛化物品背包
class PackGeneralize :public PackGroup {
public:
	//每个物品的价值函数score[i]都是一个长为v+1的数组
	static int pack01(int v, const vector<vector<int>>& score) {
		return pack01Ans(v, score)[v];
	}
protected:
	static vector<int> pack01Ans(int v, const vector<vector<int>>& score) {
		vector<vector<int>> volume2, score2;
		for (auto &s : score) {
			vector<int>vi, si;
			for (int i = 1; i <= v; i++) {
				vi.push_back(i), si.push_back(s[i]);
			}
			volume2.push_back(vi), score2.push_back(si);
		}
		return packGroup01Ans(v, volume2, score2);
	}
};
//依赖背包(仅限森林)
class PackDepend:public PackGeneralize {
public:
	//依赖背包(仅限森林),children是每个节点的子节点集,所有节点编号0到n-1,children和volume和score的长度都为n
	static int packDependTrees(int v,const vector<vector<int>>&children, const vector<int>& volume, const vector<int>& score) {
		return packDependTrees(v, getRoots(children), children, volume, score)[v];
	}
	//依赖背包(仅限森林),fas是所有根节点,children是每个节点的子节点集,所有节点编号0到n-1,children和volume和score的长度都为n
	static vector<int> packDependTrees(int v, const vector<int> &roots, const vector<vector<int>>&children, const vector<int>& volume, const vector<int>& score) {
		vector<vector<int>> score2;
		for (auto root : roots) {
			score2.push_back(packDependTree(v, root, children, volume, score));
		}
		return PackGeneralize::pack01Ans(v, score2);
	}
private:
	static vector<int> getRoots(const vector<vector<int>>&children) {
		map<int, int>m;
		for (auto &cs : children)for (auto x : cs)m[x]++;
		vector<int>roots;
		for (int i = 0; i < children.size(); i++)if (m[i] == 0)roots.push_back(i);
		return roots;
	}
	static vector<int> packDependTree(int v, int root, const vector<vector<int>>&children, const vector<int>& volume, const vector<int>& score) {
		vector<int> ans(v+1,0);
		if (volume[root] > v)return ans;
		if (children[root].empty()) {
			ans[volume[root]] = score[root];
			return ans;
		}
		return packDependTrees(v - volume[root], children[root], children, volume, score);
	}
};

一,0-1背包

0-1背包就是每个物品只有1个,只能选或者不选,一共n个物品,总体积为v。

思路:二维DP,按照任意顺序依次遍历所有物品,遍历到每个物品时,遍历容量是0到v的所有情况。

空间压缩:为了压缩辅助空间,需要控制容量遍历顺序是从大到小。

难点:一个比较难理解的点是,最后遍历的物品,实际上是最先放入背包的。

时间复杂度:O(vn)

0-1背包

HDU 2602 Bone Collector

题目:

Description

Many years ago , in Teddy’s hometown there was a man who was called “Bone Collector”. This man like to collect varies of bones , such as dog’s , cow’s , also he went to the grave … 
The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone’s value along his trip , can you calculate out the maximum of the total value the bone collector can get ? 

Input

The first line contain a integer T , the number of cases. 
Followed by T cases , each case three lines , the first line contain two integer N , V, (N <= 1000 , V <= 1000 )representing the number of bones and the volume of his bag. And the second line contain N integers representing the value of each bone. The third line contain N integers representing the volume of each bone.

Output

One integer per line representing the maximum of the total value (this number will be less than 2  31).

Sample Input

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

Sample Output

14

这个题目只是最简单的0-1背包问题,不说话了,直接上代码

代码:


#include<iostream>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;

......

int main()
{
	int cas;
	cin >> cas;
	int N, V;
	while (cas--)
	{
		cin >> N >> V;
		vector<int>value(N);
		vector<int>volume(N);
		for (int i = 0; i < N; i++)cin >> value[i];
		for (int i = 0; i < N; i++)cin >> volume[i];
		cout << Pack::pack01(V, volume,value) << endl;
	}
	return 0;
}

力扣 416. 分割等和子集

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
 

提示:

1 <= nums.length <= 200
1 <= nums[i] <= 100

const int N = 10001;

class Solution {
public:
	bool canPartition(vector<int>& nums) {
		bool res[N];
		int s = 0;
		for (int i = 0; i < nums.size(); i++)s += nums[i];
		if (s % 2)return false;
		s /= 2;
		for (int i = 1; i <= s; i++)res[i] = false;
		res[0] = true;
		for (int i = 0; i < nums.size(); i++) {
			for (int j = s; j >= nums[i]; j--)res[j] = res[j] || res[j - nums[i]];
		}
		return res[s];
	}
};

力扣 1049. 最后一块石头的重量 II

有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

  • 如果 x == y,那么两块石头都会被完全粉碎;
  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x

最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0

示例 1:

输入:stones = [2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。

示例 2:

输入:stones = [31,26,33,21,40]
输出:5

提示:

  • 1 <= stones.length <= 30
  • 1 <= stones[i] <= 100
class Solution {
public:
	int lastStoneWeightII(vector<int>& stones) {
		int s = 0;
		for (auto x : stones)s += x;
		int m = Pack::pack01(s / 2, stones, stones);
		return s - m - m;
	}
};

力扣 2742. 给墙壁刷油漆

给你两个长度为 n 下标从 0 开始的整数数组 cost 和 time ,分别表示给 n 堵不同的墙刷油漆需要的开销和时间。你有两名油漆匠:

  • 一位需要 付费 的油漆匠,刷第 i 堵墙需要花费 time[i] 单位的时间,开销为 cost[i] 单位的钱。
  • 一位 免费 的油漆匠,刷 任意 一堵墙的时间为 1 单位,开销为 0 。但是必须在付费油漆匠 工作 时,免费油漆匠才会工作。

请你返回刷完 n 堵墙最少开销为多少。

示例 1:

输入:cost = [1,2,3,2], time = [1,2,3,2]
输出:3
解释:下标为 0 和 1 的墙由付费油漆匠来刷,需要 3 单位时间。同时,免费油漆匠刷下标为 2 和 3 的墙,需要 2 单位时间,开销为 0 。总开销为 1 + 2 = 3 。

示例 2:

输入:cost = [2,3,4,2], time = [1,1,1,1]
输出:4
解释:下标为 0 和 3 的墙由付费油漆匠来刷,需要 2 单位时间。同时,免费油漆匠刷下标为 1 和 2 的墙,需要 2 单位时间,开销为 0 。总开销为 2 + 2 = 4 。

提示:

  • 1 <= cost.length <= 500
  • cost.length == time.length
  • 1 <= cost[i] <= 106
  • 1 <= time[i] <= 500
class Solution {
public:
    int paintWalls(vector<int>& cost, vector<int>& time) {
        int s=0;
        for(auto &x:time)s+=x++;
        int s2=0;
        for(auto x:cost)s2+=x;
        return s2-Pack::pack01(s,time,cost);
    }
};

​CSU 1197 Staginner 买葡萄

Description

今天,Staginner吃饭的时候捡到了Samsara丢失的饭卡,由于Staginner一直耿耿于怀上次周末Samsara在岳麓山刁难他的事情,Staginner决定把Samsara饭卡里的钱用来买自己最喜欢吃的葡萄。

当Staginner走到水果店时,刷了一下Samsara的饭卡,发现卡上还有S元钱,环顾店内,Staginner又发现此时店里卖的葡萄也很特别。这里的葡萄都是成串卖的,并且每串葡萄上面都标有一个正整数N,表示这串葡萄卖N元。Staginner发现所有葡萄串上的标号都是不同的,且恰有标号分别为2,3,...,S-1,S这样的S-1串葡萄,并且标号为N的葡萄串上的葡萄个数是N的除去自身外所有约数的和,比如标号为4的这串葡萄就一共有1+2=3个葡萄,而标号为6的这串葡萄就一共有1+2+3=6个葡萄。

现在Staginner想知道,他用这S元钱最多能买到多少个葡萄呢?当然,可以不必花完这S元钱。Staginner希望你能尽快帮他算出来,要不然趁这个时间Samsara把自己的卡挂失了可就得不偿失了。

Input

输入包含若干组数据,每组数据占一行且只包含一个正整数S(2<=S<=25),表示上

面描述的含义。

读入以EOF结束。

Output

对于每组数据,输出一个正整数表示Staginner 最多能买到的葡萄的个数。

Sample Input

11

Sample Output

9

0-1背包问题

代码:

代码一

#include<iostream>
using namespace std;

int f(int n)
{
	int r = 0;
	for (int i = 1; i < n; i++)if (n%i == 0)r += i;
	return r;
}

int main()
{
	for (int i = 1; i <= 25; i++)cout << f(i) << ',';
	return 0;
}

运行结果

代码二

#include<iostream>
#include<stdio.h>
using namespace std;

int v[26] = { 0, 0, 1, 1, 3, 1, 6, 1, 7, 4, 8, 1, 16, 1, 10, 9, 15, 1, 21, 1, 22, 11, 14, 1, 36, 6 };

int main()
{
	int ans[26];
	for (int s = 2; s <= 25; s++)
	{
		for (int i = 0; i <= s; i++)ans[i] = 0;
		for (int i = 1; i <= s; i++)for (int j = s; j >= i; j--)
			if (ans[j] < ans[j - i] + v[i])ans[j] = ans[j - i] + v[i];
			cout << ans[s] << ',';
	}	
	return 0;
}

运行结果

代码三

#include<iostream>
#include<stdio.h>
using namespace std;

int main()
{
	int ans[26] = { 0, 0, 1, 1, 3, 3, 6, 6, 7, 7, 9, 9, 16, 16, 17, 17, 19, 19, 22, 22, 23, 23, 25, 25, 36, 36 };
	int n;
	while (scanf("%d", &n) != EOF)printf("%d\n", ans[n]);
	return 0;
}

AC

或者代码四

#include<iostream>
#include<stdio.h>
using namespace std;

int main()
{
	int n,ans[13] = { 0, 1, 3, 6, 7, 9, 16, 17, 19, 22, 23, 25, 36 };
	while (scanf("%d", &n) != EOF)printf("%d\n", ans[n/2]);
	return 0;
}

AC

CSU 1945 最简单的题目

 题目:

Description

小明有一台笔记本电脑,一台台式机电脑,两台电脑的性能相同,现在小明手里有N个等待运行的程序,每个程序运行所需的时间分别为n1,n2,n3,n4……,一台电脑同一时刻只能运行一个程序,一个程序只需要运行一次。两台电脑同时开始运行,请问小明该如何分配程序在这两台电脑上运行,使得最后结束运行的电脑的运行时间最短。

Input

输入不超过30组数据,每组数据第一行为N,代表有N个等待运行的程序,第二行为N个数字,代表每个程序的运行时间,1 <= N <= 1000 ,每个程序的运行时间均为正整数, 所有程序的运行时间之和不超过5000。

Output

输出最后结束运行的电脑的运行时间。

Sample Input

2
1 1
2
1 2
3
1 2 3

Sample Output

1
2
3

思路:

首先是贪心,选出一些数,使得选出的数的总数最接近所有数的总数的一半即可。

其次是动态规划,其实就是0-1背包

代码:

#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;

int value[1001], ans[5001];

int main()
{
	int N, V;
	while (cin >> N)
	{
		V = 0;
		memset(ans, 0, sizeof(ans));
		for (int i = 1; i <= N; i++)
		{
			cin >> value[i];
			V+= value[i];
		}
		for (int i = 1; i <= N; i++)for (int j = V/2; j >= value[i]; j--)
			ans[j] = max(ans[j], ans[j - value[i]] + value[i]);
		cout << V - ans[V / 2] << endl;
	}
	return 0;
}

爱奇艺节目采购

爱奇艺节目采购

0-1增强代价背包

HDU 3466 Proud Merchants

题目:

Description

Recently, iSea went to an ancient country. For such a long time, it was the most wealthy and powerful kingdom in the world. As a result, the people in this country are still very proud even if their nation hasn’t been so wealthy any more. 
The merchants were the most typical, each of them only sold exactly one item, the price was Pi, but they would refuse to make a trade with you if your money were less than Qi, and iSea evaluated every item a value Vi. 
If he had M units of money, what’s the maximum value iSea could get? 

Input

There are several test cases in the input. 
Each test case begin with two integers N, M (1 ≤ N ≤ 500, 1 ≤ M ≤ 5000), indicating the items’ number and the initial money. 
Then N lines follow, each line contains three numbers Pi, Qi and Vi (1 ≤ Pi ≤ Qi ≤ 100, 1 ≤ Vi ≤ 1000), their meaning is in the description. 
The input terminates by end of file marker. 

Output

For each test case, output one integer, indicating maximum value iSea could get. 

Sample Input

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

Sample Output

5
11

这个题目很接近0-1背包问题,有一个地方不同就是,需要q的容量才能装入p大小的东西。

这个差异,我们需要在2个地方进行修改。

第一,刷表的2层for循环,内层循环要以q为界而不是以p为界。

老实说,刚开始我以为只要改这个地方就行了,结果对于题目中给的输入样例 3 10 5 10 5 3 5 6 2 7 3 都算不对。

然后我想了一下,因为0-1背包的刷表的方法是先选后面的物品,再选前面的物品。

虽然对于0-1背包问题,随便什么顺序都是一样的,

但是这个问题里面,先选3 5 6之后,就没法选5 10 5了。

如果先选5 10 5,然后是可以选3 5 6的。

然后顺序问题要怎么样解决呢?

肯定不是对顺序进行枚举!毕竟有500个物品。

这个题目的顺序问题还是不难的,就是计算每个物品的q-p的值,然后以此为序来排序即可。

如果2个物品的q-p是一样的,我们甚至不关心这2个物品的顺序!

具体的原理我也说不太清楚,反正就感觉很显然。

简单的说,装完 i 之后能不能装 j 取决于背包有没有 pi+qj ,装完 j 之后能不能装 i 取决于背包有没有pj+qi

因为2种顺序的总价值是一样的,所以我们应该选择pi+qj和pj+qi里面较小的那个数(对应的那个顺序)。

这也就是先选择q-p较大的物品装入背包的意思。

因为刷表是从后面的物品先装入的,所以物品要按照q-p升序排序。

对于如何寻找出q-p这样的贪心策略,可以看看类似的一个题目 HDU 4864 Task 

代码:


#include<iostream>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;

......


int main()
{
	int n, m;
	while (cin >> n >> m)
	{
		vector<int>volumePlus(n), volume(n), score(n);
		for (int i = 0; i < n; i++)cin >> volume[i] >> volumePlus[i] >> score[i];
		cout << Pack::packPlus(m, volumePlus, volume, score) << endl;
	}
	return 0;
}

0-1双得分背包

UVA 12563 Jin Ge Jin Qu hao(正得分)

题目:

There is one very popular song called Jin Ge Jin Qu(). It is a mix of 37 songs, and is extremely long (11 minutes and 18 seconds) — I know that there are Jin Ge Jin Qu II and III, and some other unofficial versions. But in this problem please forget about them.

Why is it popular? Suppose you have only 15 seconds left (until your time is up), then you should select another song as soon as possible, because the KTV will not crudely stop a song before it ends (people will get frustrated if it does so!). If you select a 2-minute song, you actually get 105 extra seconds! ....and if you select Jin Ge Jin Qu, you’ll get 663 extra seconds!!! Now that you still have some time, but you’d like to make a plan now. You should stick to the following rules:

  •  Don’t sing a song more than once (including Jin Ge Jin Qu). 
  •  For each song of length t, either sing it for exactly t seconds, or don’t sing it at all. 
  •  When a song is finished, always immediately start a new song.

Your goal is simple: sing as many songs as possible, and leave KTV as late as possible (since we have rule 3, this also maximizes the total lengths of all songs we sing) when there are ties.


Input 

The first line contains the number of test cases T (T ≤ 100). Each test case begins with two positive integers n, t (1 ≤ n ≤ 50, 1 ≤ t ≤ 10^9), the number of candidate songs (BESIDES Jin Ge Jin Qu) and the time left (in seconds). The next line contains n positive integers, the lengths of each song, in seconds. Each length will be less than 3 minutes — I know that most songs are longer than 3 minutes. But don’t forget that we could manually “cut” the song after we feel satisfied, before the song ends. So here “length” actually means “length of the part that we want to sing”.
It is guaranteed that the sum of lengths of all songs (including Jin Ge Jin Qu) will be strictly larger than t.


Output
For each test case, print the maximum number of songs (including Jin Ge Jin Qu), and the total lengths of songs that you’ll sing.


Explanation: 

In the first example, the best we can do is to sing the third song (80 seconds), then Jin Ge Jin Qu for another 678 seconds. In the second example, we sing the first two (30+69=99 seconds). Then we still have one second left, so we can sing Jin Ge Jin Qu for extra 678 seconds. However, if we sing the first and third song instead (30+70=100 seconds), the time is already up (since we only have 100 seconds in total), so we can’t sing Jin Ge Jin Qu anymore!
 

Sample Input

3 100

60 70 80 

3 100

30 69 70


Sample Output
Case 1: 2 758 

Case 2: 3 777

这个题目其实就是相当于留下最后一秒唱劲歌金曲,然后求剩下的时间里面,最多可以唱多少歌,

同时,在保证唱歌数量最多的情况下,求最多可以唱多少时间。

代码:

#include<iostream>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;

。。。。。。

int main()
{
	int cas;
	int n, t;
	cin >> cas;
	for (int i = 1; i <= cas; i++)
	{
		cin >> n >> t;
		vector<int>volume(n);
		vector<int>score(n, 1);
		for (int i = 0; i < n; i++)
		{
			cin >> volume[i];
		}
		cout << "Case " << i << ": ";
		auto ans = Pack::packDoubleScore(t - 1, volume, score, volume);
		cout << ans[0] + 1 << " " << ans[1] + 678 << endl;
	}
	return 0;
}

这个代码是AC了的。

01背包组合计数

力扣 494. 目标和

给你一个非负整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :

  • 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:

输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3

示例 2:

输入:nums = [1], target = 1
输出:1

提示:

  • 1 <= nums.length <= 20
  • 0 <= nums[i] <= 1000
  • 0 <= sum(nums[i]) <= 1000
  • -1000 <= target <= 1000
class Solution {
public:
	int findTargetSumWays(vector<int>& nums, int target) {
		for (auto x : nums)target += x;
		if (target % 2 || target < 0)return 0;
		target /= 2;
		return Pack::pack01Num(target, nums);
	}
};

二,完全背包

完全背包就是每种物品有无限个,一共n种物品,总体积为v。

(1)预处理优化

如果一个物品A的体积大于等于物品B的体积,但是A的得分小于等于B的得分,那么就可以直接删掉物品A。

这个思路,在不同的极端场景下,可能优化效果极佳(删的只剩1个物品),也可能彻底失效(1个物品都没删掉)

(2)求解思路一:转化成0-1背包

每个物品A能取的数量其实也还是有限的,假设v除以A的体积等于x,那么可以理解成x个A,每个A可以选或者不选,转化成0-1背包。

显然该思路存在大量重复运算,时间复杂度O(vs),s是每个物品能放入的数量总和。

于是又有个优化思路:把x个A分解成log(x)个物品,他们分别是A,2A,4A,8A......

这样转化的0-1背包问题,时间复杂度变成O(vm),m是每个物品能放入的数量的对数的总和。

这个时间复杂度还是高于0-1背包的时间复杂度。

(3)求解思路二:直接求解

其实完全背包直接求解的思路和0-1背包非常相似,也是二维DP,只需要控制容量遍历顺序是从小到大即可。

时间复杂度:O(vn)

完全背包

暂无实例

完全增强代价背包

暂无实例

完全双得分背包

力扣 322. 零钱兑换(负得分)

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1
示例 2:

输入:coins = [2], amount = 3
输出:-1
示例 3:

输入:coins = [1], amount = 0
输出:0
 

提示:

1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104

class Solution {
public:
	int coinChange(vector<int>& coins, int amount) {
		auto ans = Pack::packCompleteDoubleScore(amount, coins, coins, vector<int>(coins.size(), -1));
		if (ans[0] == amount)return -ans[1];
		return -1;
	}
};

完全背包组合计数

力扣 518. 零钱兑换 II

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。 

题目数据保证结果符合 32 位带符号整数。

示例 1:

输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。

示例 3:

输入:amount = 10, coins = [10] 
输出:1

提示:

  • 1 <= coins.length <= 300
  • 1 <= coins[i] <= 5000
  • coins 中的所有值 互不相同
  • 0 <= amount <= 5000
class Solution {
public:
    int change(int amount, vector<int>& coins) {
        return Pack::packCompleteNum(amount,coins);
    }
};

完全背包排列计数

力扣 377. 组合总和 Ⅳ

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。

题目数据保证答案符合 32 位整数范围。

示例 1:

输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。

示例 2:

输入:nums = [9], target = 3
输出:0

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 1000
  • nums 中的所有元素 互不相同
  • 1 <= target <= 1000

进阶:如果给定的数组中含有负数会发生什么?问题会产生何种变化?如果允许负数出现,需要向题目中添加哪些限制条件?

思路一:直接DP

如果直接枚举target的值1,2,3...则会出现加法溢出,所以我干脆用备忘录写法。

class Solution {
public:
	int combinationSum4(vector<int>& nums, int target) {
        if(target==0)return 1;
        if(m.find(target)!=m.end())return m[target];
		for (auto x : nums)if (x <= target)m[target] += combinationSum4(nums,target-x);
		return m[target];
	}
    map<int,int>m;
};

思路二:完全背包

class Solution {
public:
	int combinationSum4(vector<int>& nums, int target) {
		return Pack::packCompleteNumA(target, nums);
	}
};

力扣 70. 爬楼梯

 题目:

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.  1 阶 + 1 阶
2.  2 阶
示例 2:

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1.  1 阶 + 1 阶 + 1 阶
2.  1 阶 + 2 阶
3.  2 阶 + 1 阶

思路一:斐波那契数列

class Solution {
public:
	int climbStairs(int n) {
		if (n > 45)return 0;
		int list[46];
		list[0] = list[1] = 1;
		for (int i = 2; i <= n; i++)list[i] = list[i - 1] + list[i - 2];
		return list[n];
	}
};

思路二:完全背包

class Solution {
public:
	int climbStairs(int n) {
		return Pack::packCompleteNumA(n,vector<int>{1, 2});
	}
};

三,多重背包

多重背包就是每种物品有有限个,一共n种物品,总体积为v。

(1)求解思路一:转化成0-1背包

参考完全背包。

优化思路:参考完全背包。

优化完的时间复杂度仍然高于O(vn)

(2)求解思路二:直接求解

利用递推式进行差分推导,从而化为滑动窗口最大值问题,参考滑动窗口的最大值

所以我们需要用到单调队列。

总时间复杂度:O(vn)

多重背包

HDU - 2191 悼念512汶川大地震遇难同胞——珍惜现在,感恩生活

急!灾区的食物依然短缺!
为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。
请问:你用有限的资金最多能采购多少公斤粮食呢?

后记:
人生是一个充满了变数的生命过程,天灾、人祸、病痛是我们生命历程中不可预知的威胁。
月有阴晴圆缺,人有旦夕祸福,未来对于我们而言是一个未知数。那么,我们要做的就应该是珍惜现在,感恩生活——
感谢父母,他们给予我们生命,抚养我们成人;
感谢老师,他们授给我们知识,教我们做人
感谢朋友,他们让我们感受到世界的温暖;
感谢对手,他们令我们不断进取、努力。
同样,我们也要感谢痛苦与艰辛带给我们的财富~

Input

输入数据首先包含一个正整数C,表示有C组测试用例,每组测试用例的第一行是两个整数n和m(1<=n<=100, 1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。

Output

对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。每个实例的输出占一行。

Sample

InputcopyOutputcopy
1
8 2
2 100 4
4 100 2
400
。。。。。。
int main()
{
	int cas, n, m;
	cin >> cas;
	while (cas--) {
		cin >> n >> m;
		vector<int> volume(m), num(m), score(m);
		for (int i = 0; i < m; i++) {
			cin >> volume[i] >> score[i] >> num[i];
		}
		cout << Pack::packMulti(n, volume, num, score) << endl;
	}
	return 0;
}

四,混合背包

混合背包就是把01背包,完全背包,多重背包混合起来,一共n种物品,总体积为v。

3种背包问题的解法都可以描述成,按照任意顺序,把所有物品扫描一遍,于是混合背包的解法也是这样。

扫描到每个物品时,根据数量决定执行3种更新方式中的一种即可。

时间复杂度:O(vn)

混合背包

洛谷 - U115689 混合背包

Description

一个旅行者有一个最多能用V公斤的背包,现在有n件物品,它们的重量分别是W1,W2,…,Wn,它们的价值分别为C1,C2,…,Cn。有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

Input

第一行:二个整数,V(背包容量,V<=200),N(物品数量,N<=30);
第2..N+1行:每行三个整数Wi,Ci,Pi,前两个整数分别表示每个物品的重量,价值,第三个整数若为0,则说明此物品可以购买无数件,若为其他数字,则为此物品可购买的最多件数(Pi)。

Output

仅一行,一个数,表示最大总价值

Sample 1

InputcopyOutputcopy
10 3
2  1  0
3  3  1
4  5  4
11

Hint

选第一件物品1件和第三件物品2件。

......
int main()
{
	int  n, m;
	cin >> n >> m;
	vector<int> volume(m), num(m), score(m);
	for (int i = 0; i < m; i++) {
		cin >> volume[i] >> score[i] >> num[i];
		if (num[i] == 0)num[i] = -1;
	}
	cout << Pack::packMix(n, volume, num, score) << endl;
	return 0;
}

五,双代价背包

一共n种物品,每个物品的代价分为2个独立的维度,需要同时满足2个总体积v1和v2。

时间复杂度:O(v1 * v2 * n)

0-1双代价背包

力扣 474. 一和零

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。

请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

示例 1:

输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。

示例 2:

输入:strs = ["10", "0", "1"], m = 1, n = 1
输出:2
解释:最大的子集是 {"0", "1"} ,所以答案是 2 。

提示:

  • 1 <= strs.length <= 600
  • 1 <= strs[i].length <= 100
  • strs[i] 仅由 '0' 和 '1' 组成
  • 1 <= m, n <= 100
class Solution {
public:
	int findMaxForm(vector<string>& strs, int m, int n) {
		vector<int> volume, volume2;
		for (auto s : strs) {
			int n = 0;
			for (auto c : s)if (c == '0')n++;
			volume.push_back(n);
			volume2.push_back(s.length() - n);
		}
		return Pack::packDoubleVolume(m, n, volume, volume2, vector<int>(strs.size(), 1));
	}
};

强双代价背包

暂无实例

多重双代价背包

暂无实例

混合双代价背包

暂无实例

六,分组背包

有若干组物品,每组挑选1个出来,所有挑选出来的物品再挑选数量放入背包。

若干组物品数量之和和n,总体积为v。

时间复杂度:O(vn)

分组01背包

HDU - 1712 ACboy needs your help

ACboy has N courses this term, and he plans to spend at most M days on study.Of course,the profit he will gain from different course depending on the days he spend on it.How to arrange the M days for the N courses to maximize the profit?

Input

The input consists of multiple data sets. A data set starts with a line containing two positive integers N and M, N is the number of courses, M is the days ACboy has.
Next follow a matrix A[i][j], (1<=i<=N<=100,1<=j<=M<=100).A[i][j] indicates if ACboy spend j days on ith course he will get profit of value A[i][j].
N = 0 and M = 0 ends the input.

Output

For each data set, your program should output a line which contains the number of the max profit ACboy will gain.

Sample

InputcopyOutputcopy
2 2
1 2
1 3
2 2
2 1
2 1
2 3
3 2 1
3 2 1
0 0
3
4
6
......
int main()
{
	int  n, m;
	while (cin >> n >> m) {
		if (!n)return 0;
		vector<vector<int>> volume(n, vector<int>(m)), score(n, vector<int>(m));
		for (int i = 0; i < n; i++)for (int j = 0; j < m; j++) {
			volume[i][j] = j + 1;
			cin >> score[i][j];
		}
		cout << PackGroup::pack01(m, volume, score) << endl;
	}
	return 0;
}

分组01背包组合计数

暂无实例

七,泛化物品背包

泛化物品就是,没有固定的代价和得分,得分是一个关于代价的函数。

求解思路很简单,把1个泛化物品转成1个物品组,再调用分组背包求解。

时间复杂度:O(nvv)

泛化物品背包

暂无实例

八,依赖背包

依赖背包就是,存在一些关系,拿一个物品的前提是必须要拿另外一个物品。

(1)处理有向环

如果存在有向环,则把环缩成一个点。

(2)处理有向无环图

实际上,有向无环图做背包还是太复杂了,时间复杂度非常高,一般我们只考虑森林场景。

森林场景通用解法:把一颗子树当成一个泛化物品,从而变成一个分组,总时间复杂度O(nvv / log v)

依赖背包

 CSU 1793 Outing 

题目:

Sample Input
4 4
1 2 3 4
Sample Output
4


题意:

给出一个静态链表,每个人的父亲上车他才上车,问最多可以多少人上车

思路:

这不是普通的并查集,这个题目可能会形成环

但是因为每个人只有1个父亲,所以环和环之间是不相交的。

假设每个环的大小为cicl[i],这个环上所有节点及其后代总个数为sum[i]

那么这就是一个0-1背包问题:

每个物品的大小为cicl[i],价值为sum[i],问最大价值是多少

代码:
 

#include<iostream>
using namespace std;
 
int n, k, fa[1001], visitnum[1001], vk = 0;
bool visit[1001];
int cicle[1001], value[1001], ans[1001];
 
void dfs(int s)
{
	int num = vk, ss = s;
	while (!visit[s])
	{
		visit[s] = true;
		visitnum[s] = vk++;
		s = fa[s];
	}
	if (visitnum[s] >= num)
	{
		cicle[s] = vk - visitnum[s];
		fa[s] = s;//把环拆开成树
	}	
	s = ss;
	while (s <= n && visit[s])s++;
	if (s <= n)dfs(s);
}
 
int find(int x)	//找祖先
{
	if (fa[x] == x)return x;
	return fa[x] = find(fa[x]);
}
 
int main()
{
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &fa[i]);
		visit[i] = false, value[i] = 0, cicle[i] = 12345;
	}
	dfs(1);
	for (int i = 1; i <= n; i++)
	{
		find(i);
		value[fa[i]]++;
	}
	for (int i = 0; i <= k; i++)ans[i] = 0;
	for (int i = 1; i <= n; i++)for (int j = k; j >= cicle[i]; j--)
		if (ans[j] < ans[j - cicle[i]] + value[i])ans[j] = ans[j - cicle[i]] + value[i];
	if (ans[k] > k)ans[k] = k;
	printf("%d\n", ans[k]);
	return 0;
}

九,泛背包问题(贪心)

贪心

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值