01背包模版
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N];
int v[N], w[N];
int main() {
int N, V;
cin >> N >> V;
for (int i = 1; i <= N; i++) cin >> v[i] >> w[i];
for (int i = 1; i <= N; i++)
// v倒序输出,防止数据覆盖(因为这里采用滚动数组,用到n-1的值)
for (int j = V; j >= v[i]; j--)
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[V] << endl;
return 0;
}
完全背包模版
多重背包(朴素)
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int s[N], v[N], w[N];
int f[N];
int main() {
int N, V;
cin >> N >> V;
for (int i = 1; i <= N; i++) cin >> v[i] >> w[i] >> s[i];
for (int i = 1; i <= N; i++) {
for (int j = V; j >= v[i]; j--) {
for (int k = 1; k <= s[i]; k++) {
if(j >= k * v[i])
f[j] = max(f[j], f[j - k * v[i]] + k * w[i]);
}
}
}
cout << f[V] << endl;
return 0;
}
多重背包(二进制优化)
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 12010, M = 2010;
int v[N], w[N];
int f[M];
int main() {
int N, V;
cin >> N >> V;
int cnt = 0;
for (int i = 1; i <= N; i++) {
int a, b, s;
cin >> a >> b >> s;
int k = 1;
while (k <= s) {
cnt++;
v[cnt] = k * a;
w[cnt] = k * b;
s -= k;
k *= 2;
}
// 还有剩余则再进行打包
if (s) {
cnt++;
v[cnt] = s * a;
w[cnt] = s * b;
}
}
N = cnt;
for (int i = 1; i <= N; i++) {
// 变成01背包问题后记得倒序
for (int j = V; j >= v[i]; j--) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[V] << endl;
return 0;
}
分组背包问题
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int f[N], s[N];
int v[N][N], w[N][N];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> s[i];
for (int j = 1; j <= s[i]; j++) {
cin >> v[i][j] >> w[i][j];
}
}
for (int i = 1; i <= n; i++) {
// 每组最多选一个,故背包容量倒序
for (int j = m; j >= 0; j--) {
for (int k = 1; k <= s[i]; k++) {
if(j>=v[i][k])
f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
}
}
}
cout << f[m] << endl;
return 0;
}
数字三角形
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, INF = 1e9;
int n;
int a[N][N], f[N][N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
cin >> a[i][j];
}
}
// 为下面递推方便,i从0 ~ n
for (int i = 0; i <= n; i++) {
// j 从 0 到 i + 1
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], f[i - 1][j]) + a[i][j];
}
}
int res = -INF;
for (int i = 1; i <= n; i++) res = max(res, f[n][i]);
cout << res << endl;
return 0;
}
最长上升子序列(朴素)
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n;
// f 表示以 i 结尾的递增子序列最长长度
int f[N], a[N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
// 初始化:至少长度为 1
f[i] = 1;
for (int j = 1; j < i; j++) {
if (a[i] > a[j]) f[i] = max(f[i], f[j] + 1);
}
}
int res = 0;
for (int i = 1; i <= n; i++) res = max(res, f[i]);
cout << res << endl;
return 0;
}
最长上升子序列(二分法优化)
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
// q 表示长度为 r 的子序列中结尾最小的
int a[N], q[N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
int len = 0;
for (int i = 1; i <= n; i++) {
int l = 0, r = len;
while (l < r) {
int mid = l + r + 1 >> 1;
if (q[mid] < a[i]) l = mid;
else r = mid - 1;
}
len = max(len, r + 1);
q[r + 1] = a[i];
}
cout << len << endl;
return 0;
}
最长公共子序列
#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() {
cin >> n >> m >> a + 1 >> b + 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
// 不管i , j 位是否相同, 都先从分别回退一位中取最大值
f[i][j] = max(f[i - 1][j], f[i][j - 1]);
if (a[i] == b[j])
// 若相同, 再跟都回退一位基础上加上一比较
f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
}
}
cout << f[n][m] << endl;
return 0;
}
最短编辑距离
思路: 和最长公共子序列类似,不过递推规则有变动, 还要讨论该位不相同的变化
#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() {
cin >> n >> a + 1 >> m >> b + 1;
for (int i = 1; i <= n; i++) f[i][0] = i;
for (int j = 1; j <= m; j++) f[0][j] = j;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
// 分别回退要加一(删除)
f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
// 相同则只需比现在和均回退一位的情况
if (a[i] == b[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);
// 否则两位需要改成一致,加一
else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
}
}
cout << f[n][m] << endl;
return 0;
}
编辑距离
合并石子
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 310;
int n;
int a[N], s[N];
int f[N][N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
// 预处理,计算前缀和
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];
// 外层循环枚举区间长度
for (int len = 2; len <= n; len++) {
// 内层循环枚举起点
for (int i = 1; i <= n - len + 1; i++) {
int l = i, r = i + len - 1;
// 初始值设为无穷大
f[l][r] = 1e8;
// k 的范围为 l 到 r - 1 , 因为 k + 1 < r
for (int k = l; k < r; k++) {
// 最后面要加上 s[r] - s[l - 1] ,前缀和计算这段新的合并代价
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
}
}
}
cout << f[1][n] << endl;
return 0;
}
整数的划分
#include <iostream>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int n;
int f[N];
int main() {
cin >> n;
f[0] = 1;
// 滚动数组: f[i][j] = f[i - 1][j] + f[i][j - 1];
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
f[j] = (f[j] + f[j - i]) % mod;
}
}
cout << f[n] << endl;
return 0;
}
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
// 获得 l 到 r 的数字(从左向右看)
int get(vector<int> num, int l, int r) {
int res = 0;
for (int i = l; i >= r; i--) res = res * 10 + num[i];
return res;
}
int power10(int n) {
int res = 1;
while (n) res *= 10, n--;
return res;
}
int count(int n, int x) {
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--) {
// 第一种情况: 前面的数在1 ~ abc -1 之间
if (i < n - 1) {
// 前面的数字乘上后面随便取
res += get(num, n - 1, i + 1) * power10(i);
if (!x) res -= power10(i);
}
// 该位正好一样,卡住了,则后面可取0 ~ def (加1是0全0的情况)
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 > a, 故若 a > b 需要换序
if (a > b) swap(a, b);
for (int i = 0; i <= 9; i++) {
// 前缀和思想
cout << count(b, i) - count(a - 1, i) << " ";
}
cout << endl;
}
return 0;
}
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 12, M = 1 << N;
int n, m;
// f 为第 i 列 第 j 种状态(二进制) 的可行方案数
LL f[N][M];
// st 表示当前列所有连续空格是否均为偶数个,否则不对
bool st[M];
vector<int> state[M];
int main() {
while (cin >> n >> m, n || m) {
for (int i = 0; i < 1 << n; i++) {
int cnt = 0;
bool is_valid = true;
for (int j = 0; j < n; j++) {
// 某位被占用,连续的一段结束,结算
if (i >> j & 1) {
// 出现奇数个空格,不对
if (cnt & 1) {
is_valid = false;
break;
}
置零,重新开始计数
cnt = 0;
}
else cnt++;
}
// 最后一段可能未遇到障碍,故仍需检查
if (cnt & 1) is_valid = false;
st[i] = is_valid;
}
for (int i = 0; i < 1 << n; i++) {
// 注意清空,state[i] 表示 i (二进制情况) 下可作为来源的各个状态
state[i].clear();
for (int j = 0; j < 1 << n; j++) {
// i & j 表示所有位都没有冲突,即不出现同时在该位的情况
// i | j 表示 i 和 j 共同作用后的状态仍满足st
if ((i & j) == 0 && st[i | j]) {
state[i].push_back(j);
}
}
}
memset(f, 0, sizeof f);
f[0][0] = 1;
// 遍历所有列
for (int i = 1; i <= m; i++) {
// 对于该列所有状态找前面可转移的状态
for (int j = 0; j < 1 << n; j++) {
// 遍历 j 符合条件的前置状态
for (auto k : state[j]) {
f[i][j] += f[i - 1][k];
}
}
}
// 输出第 m 列, 没有任何一格被前一列占用
cout << f[m][0] << endl;
}
return 0;
}
最短Hamilton路径
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 20, M = 1 << N;
int n;
// f 记录在 i (二进制) 状态下, 到达点 j 的最短距离, w 记录两点之间距离(注意i 记录所有情况,未必遍历所有点)
int f[M][N], w[N][N];
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);
// 设定出发点,从0出发经过1到0的路为0
f[1][0] = 0;
for (int i = 0; i < 1 << n; i++) {
for (int j = 0; j < n; j++) {
// 当前路径 i 要 经过点 j
if (i >> j & 1) {
for (int k = 0; k < n; k++) {
// 当前路径i 要经过点 k
if (i >> k & 1) {
// i 到 j 的最短路 = min(i 不经过 j 到 k 的最短路加上 k 到 j 的距离, 原先值)
// i - 1 << j 表示去除 i 中经过 j 的所有路(因为原先经过j ,故将该位减去即置零,就是不经过)
f[i][j] = min(f[i - (1 << j)][k] + w[k][j], f[i][j]);
}
}
}
}
}
// 答案为经过所有点(111...1) ,最后到 [n - 1] 的最短路径
cout << f[(1 << n) - 1][n - 1] << endl;
return 0;
}
没有上司的舞会
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 6010;
int n;
// f 的第一个维度代表编号,第二个维度代表选/不选
int f[N][2], happy[N];
bool has_father[N];
int h[N], e[N], ne[N], idx;
// 用领接表存上司——下属关系
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];
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
// 将下面的子节点全部探索完毕
dfs(j);
//
f[u][1] += f[j][0];
// 自己不参加,则加上下属参加/不参加中较大的那个
f[u][0] += max(f[j][0], f[j][1]);
}
}
int main() {
cin >> n;
// 不要忘了初始化邻接表
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i++) cin >> happy[i];
for (int i = 0; i < n - 1; i++) {
int a, b;
cin >> a >> b;
// 注意 b 是 a 的父节点,要倒过来(新添加的是原节点的子节点)
add(b, a);
has_father[a] = true;
}
// 找出祖宗节点:没有父节点者
int root = 1;
while (has_father[root]) root++;
// 从祖宗节点开始探索
dfs(root);
// 取祖宗参与 / 不参与的最大值
cout << max(f[root][0], f[root][1]) << endl;
return 0;
}
滑雪
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 310;
int n, m;
int f[N][N], g[N][N];
int dfs(int x, int y) {
// 用引用方便后面写
int& v = f[x][y];
// 若已计算过,直接返回
if (v != -1) return f[x][y];
// 方向数组
int dx[] = { -1,0,0,1 };
int dy[] = { 0,-1,1,0 };
v = 1;
for (int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
// 要求: 不越界且比当前地势低才可到达
if (a > 0 && a <= n && b > 0 && b <= m && g[a][b] < g[x][y]) {
// 满足条件则取当前值和(a,b) 点经过最大路径长 + 1
v = max(v, dfs(a, b) + 1);
}
}
return v;
}
int main() {
cin >> n >> m;
memset(f, -1, sizeof f);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> g[i][j];
}
}
int res = 0;
// 每个点都试试,不知道从哪出发最大
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
res = max(dfs(i, j), res);
}
}
cout << res << endl;
return 0;
}