动态规划DP

背包问题

01背包问题

在这里插入图片描述
二维

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;//n表示所有物品个数,m表示背包容量
int v[N], w[N];//v表示物品体积,w表示物品价值
int f[N][N];//f表示状态
//f[i][j]表示在前i给物体中,体积不超过j所能装下的最大价值

int main() {
	cin >> n >> m;//读入物品个数和背包容量
	for (int i = 1; i <= n; i++)//读入所有物品
		cin >> v[i] >> w[i];

	//状态计算-集合划分
//	将集合划分为选法不含i和选法含i的两个集合
	for (int i = 1; i <= n; i++)//枚举所有物品
		for (int j = 1; j <= m; j++) {//枚举所有体积
			f[i][j] = f[i - 1][j];//1.选法不含i
//			2.选法含i
			if (j >= v[i])//保证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;
}

一维

#include <iostream>
#include <algorithm>
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--)
			f[j] = max(f[j], f[j - v[i]] + w[i]);
	cout << f[m] << endl;
	return 0;
}

完全背包问题

在这里插入图片描述
//状态划分:第i个物品选k个,要满足k*v[i]<=j

#include <iostream>
#include <algorithm>
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++)
			for (int k = 0; k * v[i] <= j; k++)
				f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + w[i] * k);

	cout << f[n][m] << endl;
	return 0;
}

优化

#include <iostream>
#include <algorithm>
using namespace std;
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 = 1; j <= m; j++) {
			f[i][j] = f[i - 1][j]; //搭配不含i
			if (j >= v[i])//搭配含i
				f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
		}
	cout << f[n][m] << endl;
	return 0;
}

一维

#include <iostream>
#include <algorithm>
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 = v[i]; j <= m; j++)
			f[j] = max(f[j], f[j - v[i] + w[i]]);
	cout << f[m] << endl;
	return 0;
}

多重背包问题

在这里插入图片描述多重背包的暴力写法
数据范围:100

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N], w[N], s[N];
int f[N][N];

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 = 0; j <= m; j++)
			for (int k = 0; k <= s[i] && k * v[i] < j; k++)
				f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + w[i * k]);

	cout << f[n][m] << endl;
	return 0;
}

优化
数据范围:2000

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 25000, M = 2010;//2000*log2000
int n, m;
int v[N], w[N];
int f[N];

int main() {
	cin >> n >> m;

	int cnt = 0;//表示所有新的物品的编号
	for (int i = 1; i <= n; i++) {
		int a, b, s;
		cin >> a >> b >> s;//表示当前第i个物品的体积,价值和个数
		int k = 1; //从1开始分
		while (k <= s) {
			cnt++;//当前物品的编号++
			v[cnt] = a * k;
			w[cnt] = b * k;
			s -= k;
			k *= 2;//k是1,2,4,8,16,...,
		}
		if (s > 0) {//说明还剩下一些需要补上
			cnt++;
			v[cnt] = a * s;
			w[cnt] = b * s;
		}
	}

	n = cnt;

	for (int i = 1; i <= n; i++)
		for (int j = m; j >= v[i]; j--)
			f[j] = max(f[j], f[j - v[i] + w[i]]);

	cout << f[m] << endl;
	return 0;
}

分组背包问题

在这里插入图片描述

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];
int f[N];

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> s[i]; //读入每组
		for (int j = 0; j < s[i]; j++)
			cin >> v[i][j] >> w[i][j]; //读入第i组的第j个物品的体积与价值
	}
	for (int i = 1; i <= n; i++)//从前往后枚举每一组
		for (int j = m; j >= 0; j--)//从大到小枚举所有体积
			for (int k = 0; k < s[i]; k++)//枚举所有选择
				if (v[i][k] <= j)
					f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
	cout << f[m] << endl;
	return 0;
}

线性DP

数字三角形

在这里插入图片描述

//线性DP
//数字三角形
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, INF = 1e9;
int n;
int a[N][N];//表示数字三角形的每个点
int f[N][N];//f表示状态

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= i; j++)
			scanf("%d", &a[i][j]);

	for (int i = 0; i <= n; i++)
		for (int j = 0; j <= i + 1; j++) //边界点的左上和右上也要取成负无穷
			f[i][j] = -INF;

	f[1][1] =  a[1][1];
	for (int i = 2; i <= n; i++)
		for (int j = 1; j <= i; j++)
			f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]);

	int res = -INF;
	for (int i = 1; i <= n; i++)
		res = max(res, f[n][i]);
	printf("%d", res);
	return 0;
}

最长上升子序列

//线性DP
//最长上升子序列I
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5010;
int  n;
int a[N], f[N];
//f[i]是所有以第i个数结尾的上升子序列长度的最大值

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	for (int i = 1; i <= n; i++) { //从前往后计算每一个状态
		f[i] = 1;//以i为结尾的上升子序列的长度是1,只有a[i]一个数
		for (int j = 1; j < i; j++)//从1开始枚举上一个数是那个数
			if (a[j] < a[i])
				f[i] = max(f[i], f[j] + 1);
	}
	int res = 0;
	for (int i = 1; i <= n; i++)//枚举所有终点
		res = max(res, f[i]);
	printf("%d\n", res);

	return 0;
}

最长公共子序列

//线性DP
//f[i][j]表示所有在第一个序列的前i个字母中出现,
//且在二个序列的前j个字母中出现的子序列的长度的最大值
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N];

int main() {
	scanf("%d%d", &n, &m);
	scanf("%s%s", a + 1, b + 1);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) {
			f[i][j] = max(f[i][j - 1], f[i - 1][j]);//a[i]和b[j]出现一个
			if (a[i] == b[j])//a[i]和b[j]都出现且相等
				f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);//都不出现的情况+1
		}
	printf("%d\n", f[n][m]);
	return 0;
}

区间DP

石子合并

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 310;
int n;
int s[N];
int f[N][N];

//状态表示:所有将第i堆石子到第j堆石子合并成一堆石子的合并方式
int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &s[i]);

	for (int i = 1; i <= n; i++) //处理前缀和
		s[i] += s[i - 1];

	//区间长度是1,只有一堆石子,合并不需要代价
	for (int len = 2; len <= n; len++)//长度从小到大枚举所有状态
		for (int i = 1; i + len - 1 <= n; i++) {//枚举起点i
			int l = i, r = i + len - 1;//左右端点
			f[l][r] = 1e8;
			for (int k = l; k < r; k++) //枚举l~r-1
				f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
		}
	printf("%d\n", f[1][n]);
	return 0;
}

数位统计DP

计数问题

//计数问题
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int get(vector<int>num, int l, int r) { //求num这个数在r到l中出现了几次
	int res  = 0;
	for (int i  = l; i >= r; i--)
		res  = res * 10 + num[i];
	return res;
}

int power10(int x) {
	int res = 1;
	while (x--)
		res *= 10;
	return res;
}

int count(int n, int x) { //求1-n中x出现了几次
	if (!n)
		return 0;

	vector<int>num;
	while (n) {
		num.push_back(n % 10);
		n /= 10;
	}

	n = num.size();

	int res = 0;
	for (int i = n - 1 - !x; i >= 0; i--) {
		if (i < n - 1) {
			res += get(num, n - 1, i + 1) * power10(i);
			if (!x)
				res -= power10(i);
		}
		if (num[i] == x)
			res += get(num, i - 1, 0) + 1;
		else if (num[i] > x)
			res += power10(i);
	}
	return res;
}

int main() {
	int a, b;
	while (cin >> a >> b, a || b) {
		if (a > b)
			swap(a, b);
		for (int i = 0; i < 10; i++)
			cout << count(b, i) - count(a - 1, i) << ' ';
		cout << endl;
	}
	return 0;
}

状态压缩DP

蒙德里安的梦想

在这里插入图片描述

//DP后面没写完注释
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 12, M = 1 << N;
int n, m;//矩形的行数和列数
long long f[N][M]; //状态转移的方程
bool st[M];

int main() {
	int n, m;
	while (cin >> n >> m, n || m) {//只要n或m不等于0,就一直进行
		memset(f, 0, sizeof f);
		//预处理所有状态是不是不存在连续奇数个0
		for (int i = 0; i < 1 << n; i++) {
			st[i] = true;//假设当前该状态存在
			int cnt = 0;//cnt存储当前连续0的个数
			for (int j = 0; j < n; j++)
				if (i >> j & 1) {//当前这一位是1,说明上一段已经截至了
					if (cnt & 1)//判断上一段的0是否有连续奇数个
						st[i] = false;//0有奇数个,说明第i个状态不合法
					cnt = 0;//上一段连续0已经结束了,cnt清成0
				} else//如果当前这一位是0
					cnt++;
			if (cnt & 1)//如果最后一段0的个数是奇数的话
				st[i] = false;//说明第i个状态不合法
		}
		//DP过程
		f[0][0] = 1;
		for (int i = 1 ; i <= m; i++)
			for (int j = 0; j < 1 << n; j++)
				for (int k = 0; k < 1 << n; k++)
					if ((j & k) == 0 && st[j | k])
						f[i][j] += f[i - 1][k];
		cout << f[m][0] << endl;
	}
	return 0;
}

最短Hamilton路径

hamilton路径定义:从0到n-1不重不漏地经过每个点恰好一次
f[i][j]表示:所有从0走到j,走过的所有点是i的所有路径长度的最小值
在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 20, M = 1 << N;
int n;
int w[N][N];
int f[M][N];
//f[i][j]所有从0走到j,走过的所有点是i的所有路径长度的最小值

int main() {
	cin >> n;
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			cin >> w[i][j];
	memset(f, 0x3f, sizeof f);
	f[1][0] = 0;
	//从0走到0,走过的所有点只有0一个点,也就是第0位上是1,其余所有位是0
	for (int i = 0; i < 1 << n; i++)//i和j枚举所有状态
		for (int j = 0; j < n; j++)
			if (i >> j & 1)//从0走到j,说明i里面一定要包含j
				for (int k = 0; k < n; k++)//枚举从那个点转移过来
					if ((i - (1 << j)) >> k & 1)//i除去j点后,一定要包含k点
//					第k位一定是1,才可以走到第k位
						f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]);
	cout << f[(1 << n) - 1][n - 1] << endl;
	//走完了所有点,n个1,每一位上都是1
	return 0;
}

树形DP

没有上司的舞会

  1. f[u][0]所有从以u为根的子树中选择,并且不选u这个点的方案
  2. f[u][1]所有从以u为根的子树中选择,并且选择u这个点的方案
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 6010;
int n;
int happy[N];//每个点的高兴度
int h[N], e[N], ne[N], idx;//邻接表
int f[N][2];
//f[u][0]所有从以u为根的子树中选择,并且不选u这个点的方案
//f[u][1]所有从以u为根的子树中选择,并且选择u这个点的方案
bool has_father[N];//判断一下每个点是否有父节点

void add(int a, int b) {
	e[idx]  = b;
	ne[idx] = h[a];
	h[a] = idx++;
}

void dfs(int u) {//递归求一下每个状态
	f[u][1] = happy[u];//选择u这个点,需要先加上它的高兴度
	for (int i = h[u]; i != -1; i = ne[i]) {//枚举一下u的所有儿子
		int j = e[i];//j表示u的每一个儿子
		dfs(j);
		f[u][0] += max(f[j][0], f[j][1]);
		f[u][1] += f[j][0];
	}
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)//先读入所有点的高兴度
		scanf("%d", &happy[i]);

	memset(h, -1, sizeof h);
	for (int i  = 0; i < n - 1; i++) {
		int a, b;
		scanf("%d%d", &a, &b);//表示b是a的父节点
		has_father[a] = true;//a就有父节点b了
		add(b, a);
	}

	int root = 1;
	while (has_father[root])//当当前枚举的这个点有父节点,就看下一个点
		root++;//直到没有父节点为止
	dfs(root);
	printf("%d\n", max(f[root][0], f[root][1]));//两种情况取最大值
	return 0;
}

记忆化搜索

滑雪

f[i][j]:所有从(i,j)开始滑的路径的长度最大值

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 310;

int n, m;
int h[N][N];//表示每个点的高度
int f[N][N];//状态

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};//偏移量
int dp(int x, int y) {
	int &v = f[x][y];//引用,所有写v的地方等价于写成f[x][y]
	if (v != -1)//如果v!-1表示v已经算过了
		return v;

	v = 1;
	for (int i = 0; i < 4; i++) {//枚举四个方向
		int a = x + dx[i], b = y + dy[i];
		//枚举的状态在界限内,并且要走过去的高度小于当前这个点的高度
		if (a >= 1 && a <= n && b >= 1 && b <= m && h[a][b] < h[x][y])
			v = max(v, dp(a, b) + 1);
	}
	return v;
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)//读入每个点的高度
		for (int j = 1; j <= m; j++)
			scanf("%d", &h[i][j]);

	memset(f, -1, sizeof f);//表示每个点都没有算过

	int res = 0;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			res = max(res, dp(i, j));
//	dp[i][j]是求出这个状态的路径长并且返回
	printf("%d\n", res);
	return 0;
}
`
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值