作业介绍

AF

  • 选手: 位置, 国籍
  • 找到不同国籍的最近的那个人的和自己的距离
  • 左边最近和右边最近, min(左边 , 右边)
  • 左边最近 f1[i] : 与 i 位置不同国籍的人的距离,
    • 上一个就是不同的 ,a[i].x - a[i-1].x
    • 上一个国籍和当前相同, f1[i-1] + a[i].x - a[i-1].x
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5+10;
int n, dp_l[N], dp_r[N];
struct node {
	int c, x, id1, id2;
} a[N];
bool cmp1(node a, node b) {
	return a.x < b.x;
}
bool cmp2(node a, node b) {
	return a.id1 < b.id1;
}
signed main(void) {
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i].c >> a[i].x, a[i].id1 = i;
	}
	sort(a + 1, a + n + 1, cmp1);
	for (int i = 1; i <= n; i++) a[i].id2 = i;
	memset(dp_l, 0x3f, sizeof dp_l);
	for (int i = 2; i <= n; i++) {
		if (a[i].c != a[i - 1].c) dp_l[i] = min(dp_l[i], a[i].x - a[i - 1].x);
		else dp_l[i] = min(dp_l[i], dp_l[i - 1] + a[i].x - a[i - 1].x);
	}
	memset(dp_r, 0x3f, sizeof(dp_r));
	for (int i = n - 1; i >= 1; i--) {
		if (a[i].c != a[i + 1].c) dp_r[i] = min(dp_r[i], a[i + 1].x - a[i].x);
		else dp_r[i] = min(dp_r[i], dp_r[i + 1] + a[i + 1].x - a[i].x);
	}
	sort(a + 1, a + n + 1, cmp2);
	for (int i = 1; i <= n; i++) {
		cout << min(dp_l[a[i].id2], dp_r[a[i].id2]) << '\n';
	}
	return 0;
}

AG

  • 和固定, 求乘积最大, 两个数差最小

  • 对于每个物品,就是选或者不选, 容量应该是 sum/2

  • 求 1 - sum/2 之间能凑出的最大的数是多少,maxx

  • 01 背包

  • f[0] =1 , f[j] = f[j] |f[j-a[i]]

  • res = maxx * (sum - maxx)

  • 二进制枚举

  • 每一个位置要么选,要么不选,

AF

  • f[i] : 到 i 位置结束的时候,能选择的子序列的个数
  • i 位置能选的前提是, 这个位置要能在序列里面
  • f[i] 不选: f[i-1] 选择:f[i-2] + 1 , 取 max

AI

  • f[i] 调到 i 位置的最少跳跃次数
  • i 可以从哪些地方跳到 , f[i] = min(f[j] + 1)(1<=j<i) , (a[i]-a[j]<=跳跃距离)
  • 最后的落脚点是 l 位置, a[n+1] = l,

AJ

  • n 个不同的糖果 , k 个盒子, 放置的方案数
  • f[i][j] i 个糖果, j 个盒子的放置方案
  • f[i][j] : 对于第 i 个糖果, 1. 重新来一个盒子: f[i-1][j-1]
      1. 放在之前的盒子里面: (i-1)*f[i-1][j]
  • 注意取模

AK

  • 减 a 减 b 小于等于 c
  • +a ,+b 等于 n 的方案数
  • 基数:0 1 2 3 ... c f[基数] =1

AL

  • 凑出 2, 有2 就行 f[2]++
  • 凑出 20 , 当前是 0 , f[20] += f[2]
  • 凑出 202, 当前是 2 ,f[202] += f[20] , f[2]++
  • 凑出2023, 当前是 3, f[2023] += f[202]

R

  • 01背包
  • f[j] = max(f[j] , f[j-v[i]]+v[i]);

S

  • f[i] :打 i 个字需要的最小次数
  • f[i] = (f[i-1]+1, f[i/2]+1 (i是偶数))
  • f[1] = 1

T

  • f[i][j] 到 i 和 j 的最小花费
  • f[i][j] = min(f[i-1][j], f[i-1][j-1], f[i-1][j+1]) + a[i][j];
  • a[i][j] = ((i-1)*c+j) % 5
  • 20000*20000 的数组,开不出来,滚动数组
#include <bits/stdc++.h>
using namespace std;
const int N = 2e4 + 10;
int r, c, m;
int f[2][N];

int main() {
	cin >> r >> c >> m;
	memset(f, 0x3f, sizeof f);
	for (int i = 1; i <= c; i++)
		f[0][i] = 0;
	// 备份一个临时数组
	for (int i = 1 ; i <= r; i++) {
		for (int j = 1; j <= c; j++) {
			int tmp = ((i - 1) * c + j) % m;
			if (tmp == 0)
				tmp = m;
			f[i & 1][j] = tmp +  min(f[(i - 1) & 1][j], min(f[(i - 1) & 1][j - 1], f[(i - 1) & 1][j + 1]));
		}
	}
	int res = 0x3f3f3f3f;
	for (int i = 1; i <= c; i++)
		res = min(f[r & 1][i], res);
	cout << res;
	return 0;
}

U

  • 完全背包
  • n 个人分成若干小组,求积极度最大值
  • 每个小组, 人数 i (w[i] = i), 积极度 a[i] (v[i] = a[i])
  • 总容量 :n
  • 每个人一个组别: n 个组

V

  • f[i] : 到 i 位置的最少移动步数
  • f[i] = min(f[i-1]+1 , f[i-2]+1)
  • 障碍物,障碍物地的值,设置为 无穷大,

W

  • 完全背包
  • w[i] = pow(i,4), v[i] = 1

X

  • 01背包

Y

  • 求从某个点出发,恰好移动 k 步之后,到达的点可能有哪些
  • 状态: 出发的点,花费多少步数,
  • f[t][i][j] : 从 i 出发 走 t 步 是否能到达 j , 0 或者 1
  • res = sum(f[k][i][j] == 1 )
  • 转移: 类似:BFS , pair<u , t > , pair<to[u] , t+1>
  • f[t][i][to[j]] |= f[t-1][i][j] +1

Z

  • 最长上升子序列 f[i] : 以 a[i] 结尾最长上升子序列的长度
  • 多了:相邻树的间隔相同(所处的坐标之间的间隔)
  • f[i][j] : 以 a[i]结尾间隔为 j 的最长上升子序列的长度
  • f[i][j] = max(1, f[i-j][j]+1) (a[i] >a[i-j])

AA

  • f[i] :以 a[i]结尾的最大价值和
  • f[i] = max(f[i-1] , f[i-2]+s[i]-'a'+1)

AB

  • 最大字段和
  • f[i] = max(0, f[i-1]) +a[i];
  • memset(f, 0xcf, sizeof f); // 赋值为极小值

AC

  • 最大字段和 maxx
  • res = sum + maxx*(k-1)

AD

  • 对于加减后的结果大于 360 的,值需要对 360 取模
  • f[j] : 角度为 j 的能否凑出
  • f[j] : f[j%360] |= f[(j-a[i])%360]
  • f[j] : f[j%360] |= f[(j+a[i])%360]

AE

  • 砝码:不放, 放左边,放右边
  • f[i][j] : 前 i个砝码能否凑出 j ,
  • f[i][j] 1. w[i]==j , f[i][j] =1;
  •      2.  f[i][j] |= f[i-1][j+w[i]]
    
  •      3.  f[i][j] |= f[i-1][abs(j-w[i])]
    
  •      4.  f[i][j] |= f[i-1][j]   不放
    
  • res = sum(f[n][i] == 1)

01 背包

  • f[i][j] : 前 i 个物品在容量为 j 的情况下的最大价值
  • 转移方程 : f[i][j]= max(f[i-1]j , f[i-1][j-w[i]]+vi)
// 二维 01 背包
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int w[N], v[N];
int f[N][N];
int n, m;

int main() {
	cin >> m >> n;
	for (int i = 1; i <= n; i++)
		cin >> w[i] >> v[i];
	for (int i = 1; i <= n; i++) {
		for (int j = 1 ; j <= m; j++) {
			f[i][j] = f[i - 1][j]; // 不放当前物品
			if (j >= w[i])
				f[i][j] = max(f[i][j], f[i - 1][j - w[i]] + v[i]);
		}
	}
	cout << f[n][m];
	return 0;
}
  • 一个维度: f[j] : 在容量为 j 的情况下的最大价值
  • 转移方程:f[j] = max(f[j] , f[j-w[i]]+v[i]);
// 二维 01 背包
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int w[N], v[N];
int f[N];
int n, m;

int main() {
	cin >> m >> n;
	for (int i = 1; i <= n; i++)
		cin >> w[i] >> v[i];
	for (int i = 1; i <= n; i++)
		for (int j = m; j >= w[i] ; j--)
			f[j] = max(f[j], f[j - w[i]] + v[i]);
	cout << f[m];
	return 0;
}

完全背包

  • 一个维度: f[j] : 在容量为 j 的情况下的最大价值
  • 转移方程: f[j] = max(f[j] , f[j-w[i]]+v[i]);
#include <bits/stdc++.h>
using namespace std;
const int N = 1e7 + 10;
int w[N], v[N];
long long f[N];
int n, m;

int main() {
	cin >> m >> n;
	for (int i = 1; i <= n; i++)
		cin >> w[i] >> v[i];
	for (int i = 1; i <= n; i++)
		for (int j = w[i] ; j <= m; j++)
			f[j] = max(f[j], f[j - w[i]] + v[i]);
	cout << f[m];
	return 0;
}
  • 完全背包
  • 组合方案数, 排列方案数
  • 组合: 先遍历物品,再遍历容量:(先遍历物品,保证了物品只会按照顺序出现)
  • 排列: 先遍历容量,再遍历物品:(后遍历物品,无法保证物品按照顺序出现,排列)

多重背包

  • 枚举顺序: 物品 - 数量 - 容量 物品 - 容量 - 数量
#include <bits/stdc++.h>
using namespace std;
const int N = 4e4 + 10;
int w[N], v[N], c[N];
long long f[N];
int n, m;

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> v[i] >> w[i] >> c[i];
    // O(n*m*k)
	for (int i = 1; i <= n; i++)
		for (int k = 1; k <= c[i]; k++)
			for (int j = m; j >= 1; j--)
				if (j - w[i] >= 0)
					f[j] = max(f[j], f[j - w[i]] + v[i]);
	cout << f[m];
	return 0;
}
  • 二进制优化: 1 - 10 之间的数字可以用 1 2 4 3 每个数字至多用一次表示出来
  • 通过二进制优化转化成 01 背包
  • 二进制优化以后,物品的数量会变成:n*log2(k)
#include <bits/stdc++.h>
using namespace std;
const int N = 4e4 + 10;
int w[N], v[N], tot;
long long f[N];
int n, m;

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		int a, b, c;
		cin >> a >> b >> c; //  v w c
		// 二进制优化
		for (int j = 1; j <= c; j *= 2) {
			// c 可以拆出一个 j
			// 相当于 j  个物品凑成一组
			w[++tot] = b * j;
			v[tot] = a * j;
			c -= j; // 拆出了j
		}
		// 拆完以后可能有剩余
		if (c) { // c 个物品凑成一组
			w[++tot] = b * c;
			v[tot] = a * c;
		}
	}
	// 01 背包
	for (int i = 1 ; i <= tot; i++)
		for (int j = m; j >= w[i] ; j--)
			f[j] = max(f[j], f[j - w[i]] + v[i]);
	cout << f[m];
	return 0;
}
//10    1 - 10
//1 2 4 3    1-10 之间的数字可以用 1 2 4 3 表示出来
//1 2 3 4 5 6 7 8 9 10

混合背包

  • 01背包, 完全背包 ,多重背包
  • 多重二进制优化成01背包
  • 剩下 01和完全,分类讨论,如果是01 倒着循环容量, 完全正着循环容量
for (int i = 1; i <= tot; i++)
		if (vis[i])  // 标记数组,标记是 完全背包
			for (int j = t[i]; j <= T; j++)
				f[j] = max(f[j], f[j - t[i]] + c[i]);
		else
			for (int j = T; j >= t[i]; j--)
				f[j] = max(f[j], f[j - t[i]] + c[i]);

二维费用背包

  • 状态: f[j][v] 在重量为 J ,题解为 v 的情况下的最大价值,
  • 转移方程: f[j][v] = max(f[j][v] , f[j-w[i]][v-V[i]] +value[i]);
#include <bits/stdc++.h>
using namespace std;
const int N = 410;
int n, H, T;
int h[N], t[N], k[N];
int f[N][N];

int main() {
	cin >> H >> T;
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> h[i] >> t[i] >> k[i];
	for (int i = 1; i <= n; i++)
		for (int j = H; j >= h[i] ; j--)
			for (int l = T ; l >= t[i] ; l--)
				f[j][l] = max(f[j][l], f[j - h[i]][l - t[i]] + k[i]);
	cout << f[H][T];
	return 0;
}

分组背包

  • f[j] : 容量为 j 的最大价值
  • f[j] = max(f[j] , f[j-w[i]]+v[i])
  • 遍历顺序: 组别 - 容量 - 组里面的元素
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int n, m;
int w[N], v[N];
int f[N];
vector<int> a[N];

int maxx; // 最大组别数
int main() {
	cin >> m >> n;
	for (int i = 1; i <= n; i++) {
		int x;
		cin >> w[i] >> v[i] >> x;
		a[x].push_back(i); // 第 i个物品属于 x 组别
		maxx = max(maxx, x);
	}
	for (int i = 1; i <= maxx; i++)  // 组别
		for (int j = m; j >= 1; j--) {  // 容量
			for (auto k : a[i]) {
				if (j >= w[k])
					f[j] = max(f[j], f[j - w[k]] + v[k]);
			}
		}
	cout << f[m];
	return 0;
}

有依赖的背包

  • 分主件和附件, 附件依赖于主件
  • 主件和附件组合的可能 : 1. 主件 2. 主件+附件1 3.主件+附件2
    1. 主件 + 附件1 + 附件2
// 给定钱的总数,求价格和重要度的乘积的最大值
// 主件和附件, 附件只有在买了主件的前提下才能买
//  1. 主件  2. 主件+附件1    3.主件+附件2
//  4. 主件 + 附件1 + 附件2
#include <bits/stdc++.h>
using namespace std;
const int N = 32010;
int v[N], p[N];
int f[N];
int n, m;

bool vis[N]; // vis[i] = 1 , i 是主件
vector<int> a[N]; // 记录某个主件的附件的编号
int main() {
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		int x;
		cin >> v[i] >> p[i] >> x;
		if (x == 0)
			vis[i] = 1;
		else
			a[x].push_back(i); // 第 i 个属于主件  x 的附件
	}
	for (int i = 1; i <= n; i++) {
		if (vis[i] == 0)
			continue;
		for (int j = n; j >= 1; j--) {
			// 主件
			if (j >= v[i])
				f[j] = max(f[j], f[j - v[i]] + v[i] * p[i]);
			// 主件 +附件1
			if (a[i].size() > 0) {
				if (j >= v[i] + v[a[i][0]])
					f[j] = max(f[j], f[j - v[i] - v[a[i][0]]] + v[i] * p[i] + v[a[i][0]] * p[a[i][0]]);
			}
			// 主件 + 附件 2
			if (a[i].size() > 1) {
				if (j >= v[i] + v[a[i][1]])
					f[j] = max(f[j], f[j - v[i] - v[a[i][1]]] + v[i] * p[i] + v[a[i][1]] * p[a[i][1]]);
			}
			// 主件+附件1 + 附件2
			if (a[i].size() > 1) {
				if (j >= v[i] + v[a[i][1]] + v[a[i][0]])
					f[j] = max(f[j], f[j - v[i] - v[a[i][1]] - v[a[i][0]]] + v[i] * p[i] + v[a[i][1]] * p[a[i][1]] + v[a[i][0]] *
					           p[a[i][0]]);
			}
		}
	}
	cout << f[n];
	return 0;
}

背包方案数

  • 状态; f[j] : 凑出 j 的方案数量
  • 转移方程:f[j] += f[j-w[i]] , f[0] =1;
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
const int M = 1e4 + 10;
int n, m, a[N];
int f[M];

int main() {
	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 = m; j >= a[i] ; j--)
			f[j] += f[j - a[i]];
	cout << f[m];
	return 0;
}

A

  • f[i]: 到 i 为止的最大和,必须包含 a[i]
  • 转移方程:1. 只有 a[i] 一个 2. a[i]连上之前的某一段
  • f[i] = max(a[i] , f[i-1]+a[i]);
  • 注意:值可以为负数,

B

  • f[i] : 走到i的时候的最多能获得的礼物,保证 i 是一定要拿到的, 这个时候的时间一定是 a[i]
  • 拿到礼物 i 的时间a[i], 之前拿的礼物发放时间肯定在 a[i] 前面,
  • 对礼物的发放时间进行排序,依次来,
  • 转移:1. 自己一个 1 , 2. 在之前拿完某个以后到 a[i]
    1. 遍历之前所有发放时间比 当前时间早的 j, 满足条件: a[j] + t[j][i] <= a[i]
  • 注意点:struct node{int t, id; // id 摊位号}

c

  • f[i] : 到达 i 的最大分数
  • i 可以是通过 任意一个通道 a[j] 到达 , f[i-a[j]] + b[i - a[j]]

D

  • f[i] :到达 i 的方案数
  • 斐波那契一样的, f[i] = f[i-1]+f[i-2]+f[i-3]

E

  • 完全背包
  • 组合方案数, 排列方案数
  • 组合: 先遍历物品,再遍历容量:(先遍历物品,保证了物品只会按照顺序出现)
  • 排列: 先遍历容量,再遍历物品:(后遍历物品,无法保证物品按照顺序出现,排列)

F

  • 完全背包
  • 物品重量 w[i] = i*i , 价值 v[i] = 1
  • min , 初始值,正无穷, f[0] =0;

G

  • 河中心只有 0 个石墩:m 个荷叶:最多能有多少青蛙:m+1
  • 状态: f[i] : 河中心有 i 个石墩的时候,能有的青蛙的数量
  • f[0] = m+1
  • f[1] = f[0] + m+1 = 2*m + 2
  • f[2] = f[0] +f[1] + m+1
  • f[i] = f[0] +f[1] +...f[i-1] + m+1

H

  • 状态:到 i 的最多组数
  • 转移: 1. f[i]=1, (a[i]>=0) , 自己单独一组 2.f[i] = f[j] +1 和之前的部分形成一段(1- j之间 , j+1 - i 之间是新的一段)(sum[i] - sum[j]>=0)

I

  • 题目分类, 每个类别里面选无数个
  • 完全背包:题目花费的时间就是重量, 题目的分数就是价值

J

  • 合法队列:112222 111112 12 11 22
  • 题目要求就是:把不合法队列修改成合法队列的最小次数
  • 状态:f[i][j] 到 i 为止状态为 j 合法的最小次数, (i位置是 1 ,是 2 两种可能)
  • 转移:f[i][1] = f[i-1][1] + (a[i]!=1) , i 位置是1,之前的所有位置都得是 1
  • 转移: f[i][2] = min(f[i-1][1] , f[i-1][2]) + (a[i] != 2)
  • 输出 min(f[n][1] , f[n][2])

K 多重背包

  • // 多重背包的基础是 01 背包?
  • // 01 背包 倒着循环容量保证了只取一个 // 01 背包 for (int i = 1; i <= n; i++) // 遍历物品 for (int j = m; j >= w[i]; j--) // 遍历容量 f[j] = max(f[j], f[j - w[i]] + v[i]);

// 多重背包 for (int i = 1; i <= n; i++) // 遍历物品 for (int k = 1 ; k <= c[i]; k++) // 遍历数量 for (int j = m; j >= w[i]; j--) // 遍历容量 f[j] = max(f[j], f[j - w[i]] + v[i]);

  • 优化:二进制优化
  • for (int i = 1; i <= n; i++) { int a, b, c; cin >> a >> b >> c; // 二进制拆分 for (int j = 1 ; j <= c; j *= 2) { // 拆分出 j 件 w[++tot] = a * j; v[tot] = b * j; c -= j; } if (c) { w[++tot] = c * a; v[tot] = c * b; } }

L

  • 完全背包

M

  • 完全背包, 求最小值,初始化为最大值
  • f[0] = 0;
  • f[j] = min(f[j] , f[j-w[i]]+1)

N

  • 一个人只能说固定的单词
  • 判断某一句话,他能不能说 , 判断某一句话能不能凑出来
  • 状态:f[i] 第 i 位能不能匹配
  • 转移:1. 存在第 i 位置这个单词, f[i] =1
  •  2.  存在第 i 位和 i+1 位置合起来的单词, f[i+1] =1 
    
  • unordered_set

O

  • 01背包,限制了重量和体积
  • f[i][j] = max(f[i][j] , f[i-w[i]][j-v[i]]+value[i])

P

  • 层级, 点的编号
  • f[i][j] : 到达第 i 层, 第 j 个点的最小花费
  • f[0][1] = 0 , 初始化为极大值
  • f[i][j] = min(f[i][j] , f[i-1][能到达 j的点] +当前航线的花费)
  • 无需建图,直接边输入边处理

Q

  • 买饲料的花费:本身的价格 + 运费, 分别算出每一吨花费多少钱
  • 买 K 吨, 容量
  • 贪心: 优先购买每一吨花费最小的饲料
  • 多重背包: 重量: 每吨是1 价值:价格 +运费, 数量:每个商店有几吨

题目

题目
P3009   [USACO11JAN] Profits S
P6759   [USACO06OPEN] 县集市 The County Fair
P10108   [GESP202312 六级] 闯关游戏
P10250   [GESP样题 六级] 下楼梯
P10955   正整数拆分
P11246   [GESP202409 六级] 小杨和整数拆分
P1244   [NOI2000] 青蛙过河
P1569   [USACO ?] Generic Cow Protests [Source Request]
P2722   [USACO3.1] 总分 Score Inflation
P2837   [USACO08FEB] Dining Cows B
B2173   多重背包
B2174   完全背包
P6702   [COCI 2010/2011 #7] ŠEĆER
P15478   [CERC2012] Chemist' s vows
P1794   装备运输
P1796   汤姆斯的天堂梦
P2616   [USACO10JAN] Buying Feed, II S
P2925   [USACO08DEC] Hay For Sale S
B3636   文字工作
P12613   [CCC 2025 Junior] Connecting Territories
P13015   [GESP202506 六级] 学习小组
P13517   [KOI 2025 #2] 障碍物
P1679   神奇的四次方数
P2871   [USACO07DEC] Charm Bracelet S
P11964   [GESP202503 七级] 图上移动
P12175   [蓝桥杯 2025 省 Python B] 园艺
P12379   [蓝桥杯 2023 省 Python B] 松散子序列
B4133   [信息与未来 2014] 最大连续部分和
P12889   [蓝桥杯 2025 国 Java B] 情绪链路
P7774   [COCI 2009/2010 #2] KUTEVI
P8742   [蓝桥杯 2021 省 AB] 砝码称重
P11200   [JOIG 2024] 座席 2 / Seats 2
P12207   [蓝桥杯 2023 国 Python B] 划分
P15433   [蓝桥杯 2025 国 Python B] 灯塔
B4089   [CSP-X2020 山东] 勇敢的津津
P14778   [COCI 2025/2026 #3] 节日 / Festival
P10376   [GESP202403 六级] 游戏
P9420   [蓝桥杯 2023 国 B] 子 2023 / 双子数
P1048   [NOIP 2005 普及组] 采药
P1616   疯狂的采药
P1776   宝物筛选
P1833   樱花
P1507   NASA的食物计划
P1757   通天之分组背包
P1064   [NOIP 2006 提高组] 金明的预算方案
P1164   小 A 点菜
状态
已结束
题目
46
开始时间
2026-4-12 0:00
截止时间
2026-5-31 23:59
可延期
24 小时