ACwing算法提高课-第一章动态规划笔记(1)

1.数字三角形模型

(1)摘花生

#include<iostream>
#include<vector>
#include<algorithm>
#include<set>
#include<cmath>
#include<numeric>
#include<map>
using namespace std;
const int N = 100 + 10;
int a[N][N];
int f[N][N];
void func()
{
	int r, c;
	cin >> r >> c;
	for (int i = 1; i <= r; i++)
	{
		for (int j = 1; j <= c; j++)
		{
			cin >> a[i][j];
		}
	}
	for (int i = 1; i <= r; i++)
	{
		for (int j = 1; j <= c; j++)
		{
			f[i][j] = max(f[i - 1][j], f[i][j - 1]) + a[i][j];
		}
	}
	cout << f[r][c] << endl;
}
int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		func();
	}
	return 0;
}

(2)最低通行费

#include<iostream>
using namespace std;
const int N = 100 + 10;
#define ll long long
//题意:给出一个矩阵,左上角到右下角,可上下左右,最后一定要从右下角出来
// 在2*N-1的时间内走到右下角,每经过一个方格时间加一,即可以得出不能走回头路,只能向下或向右(到达第一个格子也算时间)
//状态表示:1.集合:所有从(1,1)走到(i,j)的所有路线的集合;2.属性:最小值(值),考虑初始和边界问题,本题中需特判第一列和第一行
//状态计算:集合划分,按最后一步划分:1.(i-1,j)到(i,j),注意i>1;2.(i,j-1)到(i,j)注意j>1
//f[i][j]=max(f[i-1][j],f[i][j-1])+a[i][j];
//时间复杂度O(n方)
//因为本题求的是最小值,所以需要特判边界;而摘花生求的是最大值,所以不需要特判边界
int a[N][N];
int f[N][N];
int n;
void func()
{
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			cin >> a[i][j];
			f[i][j] = 0x3f3f3f;
		}
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			//直接特判左上角
			if (i == 1 && j == 1)f[i][j] = a[i][j];
			else
			{
				if (i > 1)//只有不在第一行,才能从上面过来
				{
					f[i][j] = min(f[i][j], f[i - 1][j]+a[i][j]);
				}
				if (j > 1)//只有不在第一列,才能从左边过来
				{
					f[i][j] = min(f[i][j], f[i][j-1] + a[i][j]);
				}
			}
		} 
	}
	cout << f[n][n] << endl;
}
int main()
{
	func();
	return 0;
}

(3)方格取数

#include<iostream>
using namespace std;
const int N = 10 + 10;
#define ll long long
//题意:给出一个矩阵,左上角到右下角,只能向下或向右,最后一定要从右下角出来,且走两次,一个有值大于0的点只能走一次
//状态表示:1.集合:所有从(1,1)走到(i,j)的所有路线的集合;2.属性:最大值(值)
//状态计算:集合划分,按最后一步划分:1.(i-1,j)到(i,j);2.(i,j-1)到(i,j)
//走一次:f[i][j]=max(f[i-1][j],f[i][j-1])+a[i][j];
//走两次(同时走):f[i1][j1][i2][j2]表示所有从(1,1)(1,1)分别走到(i1,j1),(i2,j2)的路径的最大值
//处理同一个格子不能重复选择:(两个路线的总步数一样)只有在i1+j1==i2+j2时,两条路径的格子才重合
//优化状态:f[k][i1][i2]表示所有从(1,1)(1,1)分别走到(i1,k-i1),(i2,k-i2)的路径的最大值;
// k表示两条路线当前走到的格子横纵坐标之和,k=i1+j1=i2+j2(总步数)
//四种情况:1.一下二下;2.一下二右;3.一右二下;4.一右二右(四类最大值取max)
//第一种为例:一:(1,1)到(i1-1,j1)到(i1,j1);二:(1,1)到(i2-1,j2)到(i2,j2)
//f[k-1][i1-1][i2-1]若重合加上a[i1][j1],否则加上a[i1][j1]+a[i2][j2]
int x[N][N];
int f[N*2][N][N];
int n;
void func()
{
	cin >> n;
	int a, b, c;
	//只要a,b,c不是全为0就继续循环
	while(cin >> a >> b >> c,a||b||c)
	{
		x[a][b] = c;
	}
	for (int k = 2; k <= n + n; k++)
	{
		for (int i1 = 1; i1 <= n; i1++)
		{
			for (int i2 = 1; i2 <= n; i2++)
			{
				int j1 = k - i1;
				int j2 = k - i2;
				if (j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n)
				{
					int t = x[i1][j1];
					int& p = f[k][i1][i2];//引用,减少代码量
					if (i1 != i2)t += x[i2][j2];//一定不是同一个格子
					p = max(p, f[k - 1][i1 - 1][i2 - 1]+t);//一下二下
					p = max(p, f[k - 1][i1][i2 - 1]+t);//一右二下
					p = max(p, f[k - 1][i1-1][i2] + t);//一下右二
					p = max(p, f[k - 1][i1][i2] + t);//一右二右
				}
			}
		}
	}
	cout << f[n + n][n][n] << endl;
}
int main()
{
	func();
	return 0;
}

(4)传纸条

方格取数是可以走相同的点,但是最优解一定不走相同的点。
传纸条是不能走相同的点。

不论是在 方格取数 中,还是在本题中,最优解永远不会由两段相交的路径组成。

#include<iostream>
#include<vector>
#include<algorithm>
#include<set>
#include<cmath>
#include<numeric>
#include<map>
using namespace std;
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 50 + 10;
int x[N][N];
int f[2*N][N][N];
int main()
{
	int n,m;
	cin >> n>>m;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			cin >>x[i][j];
		}
	}
	for (int k = 2; k <= n+m; k++)
	{
		for (int i1 = 1; i1<k; i1++)
		{
			for (int i2 = 1; i2 <k; i2++)
			{
				int j1 = k - i1;
				int j2 = k - i2;
				
					int t = x[i1][j1];
					int& p = f[k][i1][i2];
					if (i1 != i2)t += x[i2][j2];
					p = max(p, f[k - 1][i1 - 1][i2] + t);
					p = max(p, f[k - 1][i1][i2 - 1] + t);
					p = max(p, f[k - 1][i1 - 1][i2 - 1] + t);
					p = max(p, f[k - 1][i1][i2] + t);
				
			}
		}
	}
	cout << f[n+m][n][n] << endl;
	return 0;
}

 2.最长上升子序列模型

(1)怪盗基德的滑翔翼

#include<iostream>
using namespace std;
const int N = 100 + 10;
int h[N];
int f[N];
//f[i]表示以i为起点的最长上升子序列
//<----O---->
//<----O(正向上升《)
//O---->(反向上升《)
int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		int n;
		cin >> n;
		int res = 0;
		for (int i = 1; i <= n; i++)
			cin >> h[i];
		//正向解LIS问题(飞行方向由从右向左)
		for (int i = 1; i <= n; i++)
		{
			f[i] = 1;
			for (int j = 1; j < i; j++)
			{
				if (h[i] > h[j])
				{
					f[i] = max(f[i], f[j] + 1);
				}
			 }
			res = max(res, f[i]);
		}
		//反向解LIS问题(飞行方向由从左向右)
		for (int i = n; i >= 1; i--)
		{
			f[i] = 1;
			for (int j = n; j > i; j--)
			{
				if (h[i] > h[j])
				{
					f[i] = max(f[i], f[j] + 1);
				}
			}
			res = max(res, f[i]);
		}
		cout << res << endl;
	}
	return 0;
}

(2)登山

#include<iostream>
using namespace std;
const int N = 1000+10;
//找到---》O《---的最长连续序列
int n;
int h[N];
int f[N];
int g[N];
int main()
{
	cin >> n;
	int res2 = 0, res1 = 0;
	for (int i = 1; i <= n; i++)
		cin >> h[i];
	//上升-->
	for (int i = 1; i<=n; i++)
	{
		f[i] = 1;
		for (int j=1;j<i;j++)
		{
			if (h[i] > h[j])
			{
				f[i] = max(f[i], f[j] + 1);
			}
		}
		res1 = max(res1, f[i]);
	}
	//下降<--
	for (int i = n; i >= 1; i--)
	{
		g[i] = 1;
		for (int j = n; j > i; j--)
		{
			if (h[i] > h[j])
			{
				g[i] = max(g[i], g[j] + 1);
			}
		}
		res2 = max(res2, g[i]);
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		ans = max(ans, f[i] + g[i] - 1);
		//减一为减去重合的
	}
	cout << ans << endl;
	return 0;
}

 (3)合唱队形

#include<iostream>
using namespace std;
const int N = 1000+10;
//找到---》O《---的最长连续序列
int n;
int h[N];
int f[N];
int g[N];
int main()
{
	cin >> n;
	int res2 = 0, res1 = 0;
	for (int i = 1; i <= n; i++)
		cin >> h[i];
	//上升-->
	for (int i = 1; i<=n; i++)
	{
		f[i] = 1;
		for (int j=1;j<i;j++)
		{
			if (h[i] > h[j])
			{
				f[i] = max(f[i], f[j] + 1);
			}
		}
		res1 = max(res1, f[i]);
	}
	//下降<--
	for (int i = n; i >= 1; i--)
	{
		g[i] = 1;
		for (int j = n; j > i; j--)
		{
			if (h[i] > h[j])
			{
				g[i] = max(g[i], g[j] + 1);
			}
		}
		res2 = max(res2, g[i]);
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		ans = max(ans, f[i] + g[i] - 1);
		//减一为减去重合的
	}
	cout << n-ans << endl;
	return 0;
}

 (4)友好城市

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 5000+10;
int f[N];
int main()
{
	int n;
	cin >> n;
	vector<pair<int, int>>a;
	for (int i = 0; i < n; i++)
	{
		int x, y;
		cin >> x >> y;
		a.push_back({ x,y });
	}
	sort(a.begin(), a.end());
	int res = 0;
	for (int i = 0; i < n; i++)
	{
		f[i] = 1;
		for (int j = 0; j < i; j++)
		{
			if (a[i].second > a[j].second)
			{
				f[i] = max(f[i], f[j] + 1);
			}
		}
		res = max(f[i], res);
	}
	cout << res << endl;
	return 0;
}

(5)最大上升子序列和

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 1000+10;
//最大上升子序列
int a[N];
int f[N];
int main()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		f[i] = a[i];//求总和
	}
	for (int i = 2; i <= n; i++)
	{
		for (int j = 1; j < i; j++)
		{
			if (a[j] < a[i])
			{
				f[i] = max(f[i], f[j] + a[i]);
			}
		}
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		ans = max(ans, f[i]);
	}
	cout << ans << endl;
	return 0;
}

 (6)导弹拦截

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 1000+10;
int a[N];
int f[N];
int n;
int main()
{
	while (cin >> a[n + 1])n++;
	//最多能拦截的导弹数-->最长下降子序列(可以等于)
	int ans1 = 0;
	for (int i = 1; i <= n; i++)
	{
		f[i] = 1;
		for (int j = 1; j < i; j++)
		{
			if (a[i] <= a[j])
			{
				f[i] = max(f[i], f[j] + 1);
			}
		}
		ans1 = max(ans1, f[i]);
	}
	//拦截导弹最少要配备的系统数-->最长上升子序列
	//(每个高度可以拦截此高度包括以下的导弹)
	int ans2 = 0;
	for (int i = 1; i <= n; i++)
	{
		f[i] = 1;
		for (int j = 1; j < i; j++)
		{
			if(a[i]>a[j])
			f[i] = max(f[i], f[j] + 1);
		}
		ans2 = max(ans2, f[i]);
	}
	cout << ans1 << endl;
	cout << ans2 << endl;
	return 0;
}

(7)导弹防御系统

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N =50+5;
int n;
int h[N];
int up[N], down[N];
//分别记录当前每个上升子序列的末尾数up[],和下降子序列的末尾数down[]。
//这样在枚举时可以快速判断当前数是否可以接在某个序列的后面。
bool dfs(int depth, int u, int su, int sd)
{
	// 如果上升序列个数 + 下降序列个数 > 总个数是上限,则回溯
	if (su + sd > depth)return false;
	if (u == n)return true;
	// 枚举放到上升子序列中的情况
	bool flag = false;
	for (int i = 1; i <= su; i++)
	{
	//对于上升子序列而言,我们将当前数接在最大的数后面,一定不会比接在其他数列后面更差。
		if (up[i] < h[u])
		{
			int t = up[i];
			up[i] = h[u];
			if (dfs(depth, u + 1, su, sd))return true;
			up[i] = t;
			flag = true;
			break;
			// 注意由上述证明的贪心原理,只要找到第一个可以放的序列,就可以结束循环了
		}
	}
	 // 如果不能放到任意一个序列后面,则单开一个新的序列
	if (!flag)
	{
		up[su + 1] = h[u];
		if (dfs(depth, u + 1, su + 1, sd))
			return true;
	}
	// 枚举放到下降子序列中的情况
	flag = false;
	for (int i = 1; i <= sd; i++)
	{
		if (down[i] > h[u])
		{
//对于下降子序列而言,我们将当前数接在最小的数后面,一定不会比接在其他数列后面更差。
			int t = down[i];
			down[i] = h[u];
			if (dfs(depth, u + 1, su, sd))
				return true;
			down[i] = t;
			flag = true;
			break;
		}
	}
	if (!flag)
	{
		down[sd + 1] = h[u];
		if (dfs(depth, u + 1, su, sd + 1))
			return true;
	}
	return false;
}
int main()
{
	while (cin >> n, n)
	{
		for (int i = 0; i < n; i++)
			cin >> h[i];
		int depth = 0;
		//前面枚举过的所有depth不能使题目条件都成立的都不能作为答案,而第一个枚举到能作为答案的depth
		//满足题目的要求(dfs返回1了),故跳出循环的时候depth的值就是满足题意的最小值答案
		while (!dfs(depth, 0, 0, 0))//dfs求最小值(迭代加深法)
			depth++;
		cout << depth << endl;
//depth即为导弹个数(上升子序列和下降子序列的总数)
	}
	return 0;
}

(8)最长公共上升子序列

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 3000 + 10;
int n;
int a[N], b[N];
int f[N][N];
int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> a[i];
	for (int j = 1; j <= n; j++)cin >> b[j];
	for (int i = 1; i <= n; i++)
	{
		int maxv = 1;//前缀最大值
		for (int j = 1; j <= n; j++)
		{
			f[i][j] = f[i - 1][j];
			if (a[i] == b[j])
				f[i][j] = max(f[i][j], maxv);
			if (a[i] > b[j])
				maxv = max(maxv, f[i][j] + 1);
		}
	}
	int res = 0;
	for (int i = 1; i <= n; i++)
		res = max(res, f[n][i]);
	cout << res << endl;
	return 0;
}

3.背包模型

(1)采药(01背包)

#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int f[N];
int w[N];
int t[N];
//f[i][j]表示选到第i个草药,在j时间内所或的最大价值
//方程:f[i][j]=max(f[i-1][j],f[i-1][j-t[i]]+w[i])
//01背包模型-->优化f[j]=max(f[j],f[j-t[i]]+w[i])
int main()
{
	int T, M;
	cin >> T >> M;
	for (int i = 1; i <= M; i++)
		cin >> t[i]>>w[i];
	for (int i = 1; i <= M; i++)
	{
		for (int j = T; j >= t[i]; j--)
		{
			f[j] = max(f[j], f[j - t[i]] + w[i]);
		}
	}
	cout << f[T] << endl;
	return 0;
}

(2)装箱问题(01背包)

#include<bits/stdc++.h>
using namespace std;
const int N = 30 + 10;
const int V = 20000 + 10;
int f[V];
int v[N];
//01背包
int main()
{
	int maxv,n;
	cin >>maxv>> n;
	for (int i = 1; i <= n; i++)
		cin >> v[i];
	for (int i = 1; i <= n; i++)
	{
		for (int j = maxv; j >= v[i]; j--)
		{
			f[j] = max(f[j], f[j - v[i]] + v[i]);
		}
	}
	cout << maxv - f[maxv] << endl;
	return 0;
}

(3)宠物小精灵之收服

ps:当动态规划中f[i][j]想找i或j的值可以采用循环判等取最值的方法。

#include<bits/stdc++.h>
using namespace std;
const int N = 1000 + 10;
int ball[N];
int hurt[N];
int f[N][N];
//f[i][j][p]表示在收服第i个精灵时,体力值为j,精灵球数量为p时,最大的收服小精灵数目
//f[i][j]=max(f[i-1][j][p],f[i-1][j-hurt[i]][p-ball[i]]+1)
//优化:f[j][p]=max(f[j][p],f[j-hurt[i]][p-ball[i]]+1)
int main()
{
	int n, m, k;
	cin >> n >> m >> k;
	int remain = 0;
	for (int i = 1; i <= k; i++)
	{
		cin >> ball[i] >> hurt[i];
	}
	for (int i = 1; i <= k; i++)
	{
		for (int j = m; j > hurt[i]; j--)
		{
			for (int p = n; p >= ball[i]; p--)
			{
				f[j][p] = max(f[j][p], f[j - hurt[i]][p - ball[i]] + 1);
			}
		}
	}
	//remain(剩余体力值)==max(m-j)(j是min时)
	for (int j = 1; j <= m; j++)
	{
		for (int p = 1; p <= n; p++)
		{
			if (f[j][p] == f[m][n])
			{
				remain =max(remain, m - j+1);
			}
		}
	}
	cout << f[m][n] << " " <<remain<< endl;
	return 0;
}

(4)数字组合(01背包)

#include<bits/stdc++.h>
using namespace std;
const int N = 100 + 10;
const int M = 10000 + 10;
int f[M];
int a[N];
//f[i][j]表示在选择第i个数,和为j的方案数
// 注意方案数为数量(相加)
//f[i][j]=f[i-1][j]+f[i-1][j-a[i]]
// 优化f[j]=f[j]+f[j-a[i]];
//要使和恰好为M
int main()
{
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	f[0] = 1;
	//和为0--->什么都不选也是一种方案
	for (int i = 1; i <= n; i++)
	{
		for (int j = m; j >= a[i]; j--)
		{
			f[j] = f[j] + f[j - a[i]];
		}
	}
	cout << f[m] << endl;
	return 0;
}

 (5)买书(完全背包)

#include<bits/stdc++.h>
using namespace std;
const int N = 1000 + 10;
//完全背包模型
//f[i][j]=f[i-1][j]+f[i][j-v[i]]
//优化:f[j]=f[j]+f[j-v[i]]
int f[N];
int main()
{
	int a[4] = { 10,20,50,100 };
	int n;
	cin >> n;
	f[0] = 1;
	for (int i = 0; i < 4; i++)
	{
		for (int j = a[i]; j <= n; j++)
		{
			f[j] = f[j] + f[j - a[i]];
		}
	}
	cout << f[n] << endl;
	return 0;
}

(6)货币系统(完全背包)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 15 + 10;
const int M = 3000 + 10;
//完全背包模型
//f[i][j]=f[i-1][j]+f[i][j-v[i]]
//优化:f[j]=f[j]+f[j-v[i]]
ll f[M];
int a[N];
int main()
{
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	f[0] = 1;
	for (int i = 1; i <= n; i++)
	{
		for (int j = a[i]; j <= m; j++)
		{
			f[j] = f[j] + f[j - a[i]];
		}
	}
	cout << f[m] << endl;
	return 0;
}

(7)货币系统(进阶完全背包)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 100 + 10;
const int M = 25000 + 10;
//(n,a):n,a[1..n]
//(m,b):m,b[1..m]---m,a[1..m]
//等价:任意一个x:(1)两个都可以表示x
//(2)不能被其中如何一个表示出来
//找到最小的m
//性质1:a[1..n]每个一定都可以别b[1..m]表示出来
//性质2:在最优解中b[1..m]一定是从a[1..n]中挑选出来的
//性质3:b[1..m]中任意一个都不能被其它数表示出来
//先将a[1..n]排序(没有负数,大的数被小的数表示出来)
//a[1..n]中:(1)a[i]可被其它a[1~i-1]表示,则一定不能选
//(2)a[i]不可被其它a表示,则一定选
//-->背包思想:在a[1..i-1]选体积是否为a[i](完全背包)
int a[N];
int f[M];
int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		int n;
		cin >> n;
		int m = 0;//体积最大值
		for (int i = 1; i <= n; i++)
		{
			cin >> a[i];
			m = max(m, a[i]);
		}
		memset(f, 0, sizeof(f));
		f[0] = 1;//方案数
		for (int i = 1; i <= n;i++)
		{
			for (int j = a[i]; j<=m;j++)
			{
				f[j] = f[j]+f[j - a[i]];
			}
		}
		int res = 0;
		for (int i = 1; i <= n; i++)
		{
			if (f[a[i]] == 1)
				res++;
		}
		cout << res << endl;
	}
	return 0;
}

(8)多重背包问题(单调队列优化)?

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1000 + 10;
const int M = 20000 + 10;
int n, m;
int f[M], g[M];
int q[M];
//s,v,w<=20000
//进阶多重背包:
//有n种物品,第i种物品最多有si件,每件体积为vi,价值为wi
//求不超过背包容量的最大价值
//单调队列优化(给一个序列,规定一小段长度为k,动态求出k段的最大值)
int main()
{
	cin >> n >> m;
    for (int i = 0; i < n; ++i) {
        memcpy(g, f, sizeof(f));
        int v, w, s;
        cin >> v >> w >> s;
        for (int j = 0; j < v; ++j) {
            int head = 0, tail = -1;
            for (int k = j; k <= m; k += v) {

                if (head <= tail && k - s * v > q[head])
                    ++head;

                while (head <= tail && g[q[tail]] - (q[tail] - j) / v * w <= g[k] - (k - j) / v * w)
                    --tail;

                if (head <= tail)
                    f[k] = max(f[k], g[q[head]] + (k - q[head]) / v * w);

                q[++tail] = k;
            }
        }
    }
    cout << f[m] << endl;
    return 0;
}

(9)庆功会(朴素多重背包)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 500 + 10;
const int M = 6000 + 10;
int n, m;
int v[N], w[N], s[N];
int f[M];
//多重背包(朴素)
//f[j]=max(f[j],f[j-k*v[i]]+k*w[i])
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> v[i] >> w[i] >> s[i];
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = m; j >= 0; j--)
		{
			for (int k = 0; k <= s[i] && k * v[i] <= j; k++)
			{
				f[j] = max(f[j], f[j - k * v[i]] + k*w[i]);
			}
		}
	}
	cout << f[m] << endl;
    return 0;
}

(10)混合背包问题

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1000+10;
int n, m;
int v[N], w[N], s[N];
int f[N];
//混合背包
//转换:将01背包和完全背包转化为多重背包
//01背包数量为1,完全背包数量为总体积/该物品的体积
//再将多重背包转化成一个个二进制01背包
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> v[i] >> w[i] >> s[i];
    }
    //1.全部转化为多重背包
    for (int i = 1; i <= n; i++)
    {
        if (s[i] == -1)s[i] = 1;//01背包
        else if (s[i] == 0)s[i] = m / v[i];//完全背包
        //s[i]=s-->多重背包
    }
    //2.多重背包二进制优化
    vector<pair<int, int>>good;
    for (int i = 1; i <= n; i++)
    {
        for (int k = 1; k <= s[i]; k += 2)
        {
            s[i] -= k;
            good.push_back({ k * v[i],k * w[i] });
        }
        if (s[i] > 0)
            good.push_back({ s[i] * v[i],s[i] * w[i] });
    }
    //3.优化后按照01背包
    for (auto t : good)
    {
        for (int j = m; j >= t.first; j--)
        {
            f[j] = max(f[j], f[j - t.first] + t.second);
        }
    }
    cout << f[m] << endl;
    return 0;
}

(11)二维背包问题

#include<bits/stdc++.h>
using namespace std;
const int N = 1000+10;
int n, maxv, maxm;
int v[N], m[N], w[N];
int f[N][N];
int main()
{
    cin >> n >> maxv >> maxm;
    for (int i = 1; i <= n; i++)
    {
        cin >> v[i] >> m[i] >> w[i];
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = maxv; j >= v[i]; j--)
        {
            for (int k = maxm; k >= m[i]; k--)
            {
                f[j][k] = max(f[j][k], f[j - v[i]][k - m[i]] + w[i]);
            }
        }
    }
    cout << f[maxv][maxm] << endl;
    return 0;
}

(12)潜水员(二维背包最少需要体积)

#include<bits/stdc++.h>
using namespace std;
const int N = 1000+10;
int m, n, k;
int o[N], d[N], w[N];
int f[25][80];
int main()
{
    cin >> m >> n >> k;
    for (int i = 1; i <= k; i++)
    {
        cin >> o[i] >> d[i] >> w[i];
    }
    memset(f, 0x3f3f3f3f, sizeof(f));
    f[0][0]=0;
    for (int i = 1; i <= k; i++)
    {
        for (int j = m; j >=0; j--)
        {
            for (int k = n; k >=0; k--)
            {
                f[j][k] = min(f[j][k], f[max(0,j - o[i])][max(0,k - d[i])] + w[i]);
            }
        }
    }
    cout << f[m][n] << endl;
    return 0;
}

(13)机器分配(dfs找路径)

#include<bits/stdc++.h>
using namespace std;
const int N = 20;
int n, m;
int v[N][N];
//分组背包
int f[N][N];
//f[i][j]表示,在前i个公司中选,总台数为j时的最大盈利
int path[N], cnt;
void dfs(int i, int j)
{
    if (!i)return;
    //寻找当前状态f[i][j]是从上述哪一个f[i-1][k]状态转移过来的
    for (int k = 0; k <= j; k++)
    {
        if (f[i - 1][j - k] + v[i][k] == f[i][j])
        {
            path[cnt++] = k;//先放入的,是编号靠后的公司
            dfs(i - 1, j - k);//一直dfs到i==1即i==0后返回到这里,然后直接return到主函数(都统计好了)
            return;
        }
    }
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            cin >> v[i][j];
        }
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j >= 1; j--)
        {
            for (int k = 0; k <= j; k++)//k可以为0!这个公司不分配设备
            {
                f[i][j] = max(f[i][j], f[i-1][j - k] + v[i][k]);
            }
        }
    }
    cout << f[n][m] << endl;

    dfs(n, m);
    for (int i = cnt - 1, id = 1; i >= 0; i--, id++)
        cout << id << " " << path[i] << endl;
    return 0;
}

(14)开心的金明(01背包)

#include<bits/stdc++.h>
using namespace std;
const int N = 30000+10;
const int M = 25 + 10;
int n, m;
int f[N];
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= m; i++)
    {
        int v, p;
        cin >> v >> p;
        for (int j = n; j >= v; j--)
        {
            f[j] = max(f[j], f[j - v] + v*p);
        }
    }
    cout << f[n] << endl;
    return 0;
}

(14)有依赖的背包问题(与树相关)

#include<bits/stdc++.h>
using namespace std;
const int N = 100 + 10;
int f[N][N];
//f[x][v]表示以x为子树的物品,在容量不超过v时所获得的最大价值
int v[N], w[N];
vector<int>g[N];
int n, m, root;
void dfs(int x)
{
    //点x必须选
    for (int i = v[x]; i <= m; i++)
        f[x][i] = w[x];
    //枚举x的儿子
    for (int i = 0; i < g[x].size(); i++)
    {
        int y = g[x][i];
        dfs(y);//一直到最低
        //类似分组背包,不断往上装
        for (int j = m; j >= v[x]; j--)//j的范围为v[x]~m, 小于v[x]无法选择以x为子树的物品
        {
            //分给子树y的空间不能大于j-v[x],不然都无法选根物品x
            for (int k = 0; k <= j - v[x]; k++)
            {
                f[x][j] = max(f[x][j], f[x][j - k] + f[y][k]);
            }
        }
    }
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        int fa;
        cin >> v[i] >> w[i] >> fa;
        if (fa == -1)
            root = i;
        else
            g[fa].push_back(i);
    }
    dfs(root);
    cout << f[root][m] << endl;
    return 0;
}

(15)背包问题求方案数(最大价值的方案)

01背包

#include<bits/stdc++.h>
using namespace std;
const int N = 1000 + 10;
const int mod = 1e9 + 7;
int n, m;
int v[N], w[N];
int f[N];
//f[j]表示背包体积为j时的最大价值
int cnt[N];
//cnt[j]表示背包体积为j时的方案数
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> v[i] >> w[i];
	for (int i = 0; i <= m; i++)
		cnt[i] = 1;
	//什么都不装时也为一种方案
	for (int i = 1; i <= n; i++)
	{
		for (int j = m; j >= v[i]; j--)
		{
			if (f[j] < f[j - v[i]] + w[i])
			{
				f[j] = f[j - v[i]] + w[i];
				cnt[j] = cnt[j - v[i]];
				//求最大价值的方案数
			}
			else if(f[j]== f[j - v[i]] + w[i])//相等的话方案数要加起来
			{
				cnt[j] = (cnt[j]+cnt[j - v[i]])%mod;//记得取模
			}
			//小于的话不用操作
		}
	}
	cout << cnt[m] << endl;
	return 0;
}

(16)背包问题求具体方案

#include<bits/stdc++.h>
using namespace std;
const int N = 1000 + 10;
int n, m;
int v[N], w[N];
int f[N][N];
//f[i][j]表示从第i个元素到最后一个元素的总容量为j的最优解
//i==1~n      不选第i个元素  选了第i个元素
//f[i][j]=max(f[i+1][j],   f[i+1][j-v[i]]+w[i])
int path[N],cnt;
//不能用一维因为在得出路径时判断f[j]==f[j-v[i]]+w[i]时用的都是第i层是不对的
// 应该是f[i][j]==f[i-1][j-v[i]]+w[i]不同层的i
//最优解所选物品的编号序列,且使编号序列的字典序最小
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> v[i] >> w[i];
	for (int i = n; i >=1; i--)//从后往前枚举(看定义)
	{
		for (int j = m; j>=0; 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]);
		}
	}
	//f[n][m]开始判断,无法得到字典序最小--->f[1][m]则可以
	for (int i = 1,j=m; i <= n; i++)
	{
		//注意条件判断j>=v[i]
		if (j >= v[i] && f[i][j] == f[i+1][j - v[i]] + w[i])
		{
			path[cnt++] = i;
			j -= v[i];
		}
	}
	for (int i = 0; i < cnt; i++)
		cout << path[i] << " ";
	cout << endl;
	return 0;
}

 (17)能量石

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int M = 100 + 10;
int n;
struct node
{
	int s, e, l;
	bool operator <(const node& W)const
	{
		return s * W.l < l* W.s;
	}
}stone[N];
int f[N];
//f[j]表示在时间j以内的最大获得能量
int main()
{
	int t;
	cin >> t;
	for(int k=1;k<=t;k++)
	{
		int n;
		cin >> n;
		int m = 0;//总时间
		for (int i = 0; i < n; i++)
		{
			cin >> stone[i].s >> stone[i].e >> stone[i].l;
			m += stone[i].s;
		}
		sort(stone, stone + n);
		memset(f, -0x3f, sizeof(f));
		f[0] = 0;
		for (int i = 0; i < n; i++)
		{
			int s = stone[i].s;
			int e = stone[i].e;
			int l = stone[i].l;
			for (int j = m; j >= s; j--)
				f[j] = max(f[j], f[j - s] + e - (j - s) * l);
			//若要吃,则吃完s时间后到j
		}
		int res = 0;
		for (int i = 0; i <= m; i++)
			res = max(res, f[i]);
		cout << "Case #"<<k<<": "<<res << endl;
	}
	return 0;
}

(18)金明的预算方法(二进制优化)

#include<bits/stdc++.h>
using namespace std;
const int N = 32000 + 10;
const int M = 60 + 10;
int n, m;
//----> q/q a/q b/q a b(4种方式)
//----> q/q a/q b/q c/q a b/q a c/q b c/q a b c(8种)
//主件有n个附件,有2的n次方种选择方式--->二进制优化
//分组背包-->01背包
pair<int, int>master[M];
vector<pair<int, int>>servant[M];
int f[N];
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		int v, p, q;
		cin >> v >> p >> q;
		p *= v;
		if (!q)
			master[i] = { v,p };
		else
			servant[q].push_back({ v,p });
	}
	for (int i = 1; i <= m; i++)
	{
		for (int j = n; j >= 0; j--)
		{
			//枚举每个主件的状态
			for (int k = 0; k < 1 << servant[i].size(); k++)
			{
				int v = master[i].first, p = master[i].second;
				for (int pp = 0; pp < servant[i].size(); pp++)
				{
					if ((k >> pp) & 1)
					{
						v += servant[i][pp].first;
						p += servant[i][pp].second;
					}
				}
				if (j >= v)
					f[j] = max(f[j], f[j - v] + p);
			}
		}
	}
	cout << f[n] << endl;
	return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值