序列 $dp$ $\&$ 区间 $dp$ $\&$ 背包 $dp$
序列 \(dp\) & 区间 \(dp\) & 背包 \(dp\)
序列 \(dp\) & 区间 \(dp\)
合法括号序列
长度为 \(n\) 的合法括号序列个数
一个合法括号序列存在一个位置可以划分为两部分
一个括号序列合法的充要条件是这个括号序列所有的前缀都是左括号大于等于右括号
一个合法的括号序列 删除最左侧和最右侧后不再存在一个位置可以将剩余的部分在划分成两部分 删除一个最左边一个括号后 一定会有前缀不再满足左括号大于右括号 也就是不再合法 所以这个转移不具有重复
\(ans = f_{i - 2} + f_i\)
每一个左括号找到右边第一个右括号进行匹配 匹配到之后删除 显然这是正确的
就有了一个 \(dp\)
\(f_{i, j}\) 表示前 \(i\) 个位置有 \(j\) 个左括号没有匹配
\(j = 0\) \(f_{i, j} \to f_{i + 1, j + 1}\)
\(j \ne 0\) \(f_{i, j} \to f_{i + 1, j - 1}\) 或 \(f_{i, j} \to f_{i + 1, j + 1}\)
卡特兰数
抽象为一个矩阵 不越过对角线从左下角走到右上角的路径方案数
通式
石子合并
朴素 \(dp\)
四边形不等式优化
考虑代价函数 如果满足 \(w_{i, j} + w_{i', j'} \leq w_{i, j'} + w_{i', j}\) 其中 \(i \leq i' \leq j \leq j'\) 那么称 \(w\) 满足四边形不等式 那么 \(dp\) 具有决策单调性 具体就是 \(s_{i, j} \leq s_{i, j + 1} \leq s_{i + 1, j + 1}\)
不懂
\(Grande\) \(Wachs\) 算法
\(LCS\) 计数
求给定两个串的 \(LCS\) 长度及个数
长度可以 \(O(n^2)\) 暴力转移
也可以 \(O(\log n)\) 上去搞
个数? 去世
去网上自己搜的题解 跑路了
\(f_{i, j}\) 表示 \(s\) 串到 \(i\) 位置 \(t\) 串到 \(j\) 位置时 \(LCS\) 的长度
\(g_{i, j}\) 表示 \(s\) 串到 \(i\) 位置 \(t\) 串到 \(j\) 位置时 \(LCS\) 的个数
\(f_{i, j}\) 朴素转移即可
\(g_{i, j}\) 转移时考虑多种情况
当 \(s_i = t_j\) 时 显然 \(f_{i, j}\) 从 \(f_{i - 1, j - 1}\) 位置转移过来 \(g_{i, j} = g_{i, j} + g_{i - 1, j - 1}\)
当 \(f_{i, j - 1} = f_{i, j}\) 时 \(f_{i, j}\) 可以从 \(f_{i, j - 1}\) 位置转移过来 \(g_{i, j} = g_{i, j} + g_{i, j - 1}\)
当 \(f_{i - 1, j} = f_{i, j}\) 时 \(f_{i, j}\) 可以从 \(f_{i - 1, j}\) 位置转移过来 \(g_{i, j} = g_{i, j} + g_{i - 1, j}\)
当 \(f_{i - 1, j} = f_{i, j - 1}\) 时 说明 \(f_{i - 1, j}\) 和 \(f_{i, j - 1}\) 均来自 \(f_{i - 1, j - 1}\) 所以这个位置就被加了两次 要减去一次
\(Flappy\) \(Bird\)
朴素 \(O(n^2)\) \(dp\) 每个位置考虑是否点击屏幕
曼哈顿距离转化为切比雪夫距离
听课不会 网上资料补充
若有两点的坐标为 \((x_1, y_1)\) \((x_2, y_2)\)
这两点的曼哈顿距离为 \(dis = |x_1 - x_2| + |y_1 - y_2|\)
这两点的切比雪夫距离为 \(dis = max\{|x_1 - x_2|, |y_1 - y_2|\}\)
两者的转化
将一个点 \((x, y)\) 的坐标变为 \((x + y, x - y)\) 后 原坐标系中的曼哈顿距离 = 新坐标系中的切比雪夫距离
将一个点 \((x, y)\) 的坐标变为 \(\left(\frac{x + y}{2}, \frac{x - y}2\right)\) 后 原坐标系中的切比雪夫距离 = 新坐标系中的曼哈顿距离
回到本题
横纵坐标奇偶性不同的位置无法到达
旋转坐标系 \((x, \frac{x + y}{2})\) 点
点击后: \((x + 1, y + 1)\) 不点: \((x + 1, y)\)
每一个柱子依然是一段纵坐标上的区间 扫一遍维护纵坐标可行区间即可
\(O(n)\)
/*
Time: 5.2
Worker: Blank_space
Source: P5957 [POI2017]Flappy Bird
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, m, l, r, y;
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
/*----------------------------------------函数*/
int main() {
n = read(); m = read();
for(int i = 1; i <= n; i++)
{
int x = read(), a = read(), b = read();
l -= x - y; r += x - y; l = Max(a + 1, l); r = Min(b - 1, r);
if(x & 1) r -= !(r & 1), l += !(l & 1); else r -= r & 1, l += l & 1;
if(r <= a || l >= b || r < l) return puts("NIE"), 0;
y = x;
}
printf("%d", l + y >> 1);
return 0;
}
涂色
给定长度为 \(n\) 的木板 无色 每次选择一个区间染色 问达到目标状态的最小次数
\(f_{i, j}\) 表示将区间 \([i, j]\) 涂成目标状态的最小操作次数
如果不存在一次操作可以将区间分成两块处理 即
\(s_i \ne s_j\) 时 \(f_{i, j} = min\{f_{i, k} + f_{k + 1, j}\}\)
否则还可以从 \(f_{i, j - 1}\) \(f_{i + 1, j}\) 转移得到
\(f_{i, j} = \min\{f_{i + 1, j}, f_{i, j - 1}\}\)
/*
Time: 5.2
Worker: Blank_space
Source: P4170 [CQOI2007]涂色
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int f[110][110];
char s[60];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
/*----------------------------------------函数*/
int main() {
scanf("%s", s + 1); int n = strlen(s + 1); memset(f, 63, sizeof f);
for(int i = 1; i <= n; i++) f[i][i] = 1;
for(int i = 2; i <= n; i++)
for(int l = 1, r = i; r <= n; l++, r++)
if(s[l] == s[r]) f[l][r] = Min(f[l][r], Min(f[l + 1][r], f[l][r - 1]));
else for(int k = l; k < r; k++) f[l][r] = Min(f[l][r], f[l][k] + f[k + 1][r]);
printf("%d", f[1][n]);
return 0;
}
\(Clear\) \(the\) \(String\)
给定一个串 每次可以删去同一个字符组成的一个子串 删去整个串的最小次数
\(f_{i, j}\) 表示删去 \([i, j]\) 的最小代价
考虑枚举断点 该位置与 \(r\) 位置的字符一起删去 若枚举位置为 \(k\) 若 \(s_k = s_r\) 则 \(f_{l, r} = f_{l, k} + f_{k + 1, r} - 1\)
否则 \(f_{l, r} = f_{l, k} + f_{k + 1, r}\)
/*
Time: 5.2
Worker: Blank_space
Source: CF1132F Clear the String
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, f[510][510];
char s[510];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
/*----------------------------------------函数*/
int main() {
n = read(); scanf("%s", s + 1); memset(f, 63, sizeof f);
for(int i = 1; i <= n; i++) f[i][i] = 1;
for(int i = 2; i <= n; i++)
for(int l = 1, r = i; r <= n; l++, r++)
for(int k = l; k < r; k++)
if(s[k] == s[r]) f[l][r] = Min(f[l][r], f[l][k] + f[k + 1][r] - 1);
else f[l][r] = Min(f[l][r], f[l][k] + f[k + 1][r]);
printf("%d", f[1][n]);
return 0;
}
中国象棋
P2051
\(f_{i, j, k}\) 表示考虑到第 \(i\) 行 有 \(j\) 列放置了 \(1\) 个炮 \(k\) 列放置了 \(2\) 炮
自己 yy 了一个式子 然后没过样例
翻题解发现差距比较大 转移全了 没考虑方案...
当该行不放置时 直接继承
当放置一个时
当放置两个时
很明显可以滚一下 懒得滚了 就这样了 其实是滚挂了
/*
Time: 5.2
Worker: Blank_space
Source: P2051 [AHOI2009]中国象棋
*/
/*--------------------------------------------*/
#include<cstdio>
#define int long long
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 9999973;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, m, f[110][110][110], ans;
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
int C2(int x) {return x * (x - 1) / 2;}
/*----------------------------------------函数*/
signed main() {
n = read(); m = read(); f[0][0][0] = 1;
for(int i = 1; i <= n; i++) for(int j = 0; j <= m; j++) for(int k = 0; k <= m - j; k++)
{
f[i][j][k] = f[i - 1][j][k];
if(k) f[i][j][k] = (f[i][j][k] + f[i - 1][j + 1][k - 1] * (j + 1) % mod) % mod;
if(j) f[i][j][k] = (f[i][j][k] + f[i - 1][j - 1][k] * (m - (j - 1) - k) % mod) % mod;
if(j > 1) f[i][j][k] = (f[i][j][k] + f[i - 1][j - 2][k] * C2(m - (j - 2) - k) % mod) % mod;
if(k) f[i][j][k] = (f[i][j][k] + f[i - 1][j][k - 1] * j * (m - j - (k - 1)) % mod) % mod;
if(k > 1) f[i][j][k] = (f[i][j][k] + f[i - 1][j + 2][k - 2] * C2(j + 2) % mod) % mod;
}
for(int i = 0; i <= m; i++) for(int j = 0; j <= m - i; j++) ans = (ans + f[n][i][j]) % mod;
printf("%d", ans);
return 0;
}
\(RGB\) \(Sequence\)
有 \(n\) 格子 每个格子染上红/绿/蓝中的一个颜色
有 \(m\) 个限制 每个限制形如 \((l, r, x)\) 表示 \([l, r]\) 区间内出现颜色的数量恰好是 \(x\)
设 \(f_{i, j, k, l}\) 表示当前考虑到 \(i\) 个格子 红绿蓝三种颜色最后一次出现的位置为 \(j\) \(k\) \(l\) 的方案数
\(i = \max\{j, k, l\}\) 记录后几维的状态
对于限制 把限制挂在右侧 \(dp\) 到右侧时想做考虑每个颜色的最后出现位置
不会更新 烦
翻题解...
二三维的字符上一个出现位置需要枚举 判一下 \(0\) 这个位置
用限制将不合法的卡掉 用没被卡掉的 \(i\) 位置更新 \(i + 1\) 的位置
说实话头一次见这样的转移
/*
Time: 5.2
Worker: Blank_space
Source: AT2567 [ARC074C] RGB Sequence
统计每个颜色最后出现的位置 限制放到右侧 清方案
*/
/*--------------------------------------------*/
#include<cstdio>
#include<algorithm>
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, m, f[310][310][310], p = 1, ans;
struct node {int l, r, x;} a[310];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
bool cmp(node x, node y) {return x.r < y.r;}
/*----------------------------------------函数*/
int main() {
n = read(); m = read(); f[1][0][0] = 3;
for(int i = 1; i <= m; i++) a[i] = (node){read(), read(), read()};
std::sort(a + 1, a + 1 + m, cmp);
for(int i = 1; i <= n; i++)
{
while(p <= m && a[p].r <= i)
{
for(int j = 0; j < i; j++) for(int k = 0; k < Max(j, 1); k++)
if(a[p].x == 1 && a[p].l <= j) f[i][j][k] = 0;
else if(a[p].x == 2 && (k >= a[p].l || a[p].l > j)) f[i][j][k] = 0;
else if(a[p].x == 3 && k < a[p].l) f[i][j][k] = 0;
p++;
}
if(i < n) for(int j = 0; j < i; j++) for(int k = 0; k < Max(j, 1); k++) if(f[i][j][k])
{
f[i + 1][j][k] = (f[i + 1][j][k] + f[i][j][k]) % mod;
f[i + 1][i][k] = (f[i + 1][i][k] + f[i][j][k]) % mod;
f[i + 1][i][j] = (f[i + 1][i][j] + f[i][j][k]) % mod;
}
}
for(int i = 0; i < n; i++) for(int j = 0; j < Max(i, 1); j++) ans = (ans + f[n][i][j]) % mod;
printf("%d", ans);
return 0;
}
\(Myj\)
\(n\) 家洗车店 \(m\) 个人洗车 第 \(i\) 个人驶过 \(a_i\) 到 \(b_i\) 个洗车店 选择最便宜的进行一次消费 如果最便宜的价格大于 \(c_i\) 则不会消费 为每家店指定价格 使得所有人花的钱总和最大
把 \(c\) 离了 区间 \(dp\)
设 \(f_{i, j, k}\) 为 区间 \([i, j]\) 最小值为 \(k\) 时的最大收益 转移时枚举最小值所在的位置 \(x\) 用 \(f_{i, x - 1, \geq k} + f_{x + 1, j, \geq k} + cost_x\) 来更新 \(f_{i, j, k}\)
依旧不知道怎么写 做的最蒙的一个 \(dp\)
大概流程
录入 对 \(c\) 进行离散化 目的是能够作为数组下标 实际上计算价值的时候并没有用
外层两层枚举左右端点 正常的区间 \(dp\) 内部先对这一区间的人数进行统计
枚举断点与花费 进行清空 枚举所有人 累计个数 低的花费可以由较高的花费转移
进行 \(dp\) 枚举断点和花费 转移的同时注意记录断点位置 \(dp\) 方程不只有一个 还有可能人数较少 价格较高 同样要进行转移 要记录转移时的价值
\(dfs\) 统计答案
/*
Time: 5.2
Worker: Blank_space
Source: P3592 [POI2015]MYJ
*/
/*--------------------------------------------*/
#include<cstdio>
#include<algorithm>
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, m, a[4010], b[4010], c[4010], d[4010], f[60][60][4010], V[60][60][4010], tot, ans[4010], pos[60][60][4010], g[60][4010];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
void dfs(int l, int r, int k) {
if(l > r) return ; int p = pos[l][r][(k = V[l][r][k])];
ans[p] = d[k]; dfs(l, p - 1, k); dfs(p + 1, r, k);
}
/*----------------------------------------函数*/
int main() {
n = read(); m = read();
for(int i = 1; i <= m; i++) a[i] = read(), b[i] = read(), c[i] = d[i] = read();
std::sort(d + 1, d + 1 + m); tot = std::unique(d + 1, d + 1 + m) - d - 1;
for(int i = 1; i <= m; i++) c[i] = std::lower_bound(d + 1, d + 1 + tot, c[i]) - d;
for(int l = n; l; l--) for(int r = l; r <= n; r++)
{
for(int k = l; k <= r; k++) for(int v = 0; v <= tot; v++) g[k][v] = 0;
for(int p = 1; p <= m; p++) if(l <= a[p] && b[p] <= r) for(int k = a[p]; k <= b[p]; k++) g[k][c[p]]++;
for(int k = l; k <= r; k++) for(int v = tot - 1; v; v--) g[k][v] += g[k][v + 1];
for(int v = tot, val, max = 0; v; v--, max = 0)
{
for(int k = l; k <= r; k++) if((val = f[l][k - 1][v] + f[k + 1][r][v] + g[k][v] * d[v]) >= max) max = val, pos[l][r][v] = k;
if(max >= f[l][r][v + 1]) f[l][r][v] = max, V[l][r][v] = v; else f[l][r][v] = f[l][r][v + 1], V[l][r][v] = V[l][r][v + 1];
}
}
dfs(1, n, 1);
printf("%d\n", f[1][n][1]);
for(int i = 1; i <= n; i++) printf("%d ", ans[i]);
return 0;
}
\(String\) \(Werght\)
定义一份字符串的重量为对于出现的所有字符 其最后一次的位置减去第一次的位置的和
定义一个字符串是轻的当且仅当他是所有同长度的字符串里重量最小的之一
选择 \(n\) 个轻的字符串 将他们顺次相连 第 \(i\) 个字符串的长度是\(L_i\) 求最后得到的字符串的重量最小是多少
考虑一个子字符串中出现的字符 有四种情况
- 总串中唯一一次出现
- 第一次出现
- 最后一次出现
- 不是第一次也不是最后一次
在一个子字符串中 贪心的将前两种情况放到最后 第三种放到前面 其他两类放到中间 如果知道每种情况的出现次数就可以得出这个字符串对答案的贡献
\(dp\) 记下当前已经使用过的字母个数和已经不能再使用的字母个数 每次枚举子字符串中各种情况的次数
\(f_{i, j, k}\) 表示 第 \(i\) 个字符串 有 \(j\) 个字母已经出现还没结束 \(k\) 个已经结束 \(26 - j - k\) 个未出现
\(Fox\) \(And\) \(Flower\) \(Shop\)
给定 \(n \times m\) 的网络 每个格子上有 \(0\) 或 \(\pm 1\) 要求选出互不相交的矩形 其中数字的和的绝对值不超过 \(T\) 求最大值
必定存在一掉横线或竖线分割两个矩形 预处理 \(f_{i, j}\) 表示前 \(i\) 行和为 \(j\) 的矩形的最大绝对值和 后 \(i\) 行同理 枚举分割线更新答案 列同理
\(O(n^2m^2)\)
\(f_{i, j}\) 表示前 \(i\) 行和为 \(j\) 的矩形的绝对值的和的最大值
\(g_{i + 1, k}\) 表示第 \(i + 1\) 到 \(n\) 行和为 \(k\) 的矩形的绝对值的和的最大值
当 \(|j + k| \leq T\) 时更新答案
直接更新不可行 转换:
\(-T \leq j + k \leq T\)
\(-T - k \leq j \leq T - k\)
建树状数组 查询一个范围内的最大值
\(k\) 是一个范围 可以单调队列维护
\(Planet\) \(of\) \(singles\)
给定两个长度为 \(n\) 的 \(01\) 串 \(s\) \(t\)
有三种操作 将第一个串
- 一个 \(0\) 改为 \(1\) 代价为 \(t_0\)
- 一个 \(1\) 改为 \(0\) 代价为 \(t_1\)
- 交换相邻两个位置 代价为 \(t_2\)
问两串完全相同的代价
通过第三个操作交换任意两个位置 \(i\) \(j\) 上的字符 代价为 \(|i - j| \times t_2\) (相同字符没有交换意义 不需要两倍)
相同位置不操作 仅考虑不同位置
考虑一个新的字符串
如果 \(s_i = 0\) \(t_i = 1\) 则 \(c_i = 0\)
如果 \(s_i = 1\) \(t_i = 0\) 则 \(c_i = 1\)
对 \(c\) 进行 \(dp\)
交换相当于将 \(c\) 串中的 \(0\) 与 \(1\) 匹配 更改与原操作等价
对于两个交换 交换不交叉 对于一对交换 其中所有的处理一定是通过交换实现的
预处理每个位置 找到前面第一个满足他们之间 \(0\) \(1\) 个数相同的位置 \(p_i\)
\(i\) 可以选择修改也可以选择和 \(p_i\) 交换 代价为两点之间的所有位置交换的代价
管道取球
管道上面有 \(n\) 个珠子 下面 \(m\) 个珠子 每个珠子是黑色或白色 每次可以从上管道或下管道末端去出一个珠子 设最后得到的不同的序列有 \(T\) 中 有 \(a_i\) 种取法可以得到第 \(i\) 中序列 求 \(\sum_{i = 1}^{T} a_i^2\)
\(\sum a_i^2\) 直接做不好处理 考虑组合意义
如果答案表示让两个人分别取珠子 最后取得的序列相同的方案数之和
\(f_{k, i, j}\) 表示两个人都取了 \(k\) 颗珠子 第一个人在上方水管中取了 \(i\) 颗 第二个人在上方水管中取了 \(j\) 颗 得到的序列相同的方案总数 枚举前一个两个人分别取得哪个管道里的珠子转移即可
/*
Time: 5.2
Worker: Blank_space
Source: P1758 [NOI2009] 管道取珠
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1024523;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, m, f[2][510][510];
char a[510], b[510];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
/*----------------------------------------函数*/
int main() {
n = read(); m = read(); scanf("%s%s", a + 1, b + 1); f[0][0][0] = 1;
for(int i = 1; i <= n + m; i++)
{
for(int j = 0; j <= n; j++) for(int k = 0; k <= n; k++) f[i & 1][j][k] = 0;
for(int j = 0; j <= n; j++) for(int k = 0; k <= n; k++)
{
if(a[j] == a[k]) f[i & 1][j][k] = (f[i & 1][j][k] + f[i + 1 & 1][j - 1][k - 1]) % mod;
if(a[j] == b[i - k]) f[i & 1][j][k] = (f[i & 1][j][k] + f[i + 1 & 1][j - 1][k]) % mod;
if(b[i - j] == a[k]) f[i & 1][j][k] = (f[i & 1][j][k] + f[i + 1 & 1][j][k - 1]) % mod;
if(b[i - j] == b[i - k]) f[i & 1][j][k] = (f[i & 1][j][k] + f[i + 1 & 1][j][k]) % mod;
}
}
printf("%d", f[n + m & 1][n][n]);
return 0;
}
背包
\(01\) 背包
容量和价值可以互换
完全背包
...
多重背包
二进制拆分
单调队列优化
按照模 \(v_i\) 的余数进行分类 每个余数维护一个单调队列
设当前余数为 \(x\) 令
有
单调队列优化上面这个东西
\(Fire\)
背包问题 由于 \(b\) 的限制 \(b\) 小的物品无法从较大的物品转移 先排序 跑 \(01\)
/*
Time: 5.2
Worker: Blank_space
Source: CF864E Fire
*/
/*--------------------------------------------*/
#include<cstdio>
#include<vector>
#include<algorithm>
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, m, f[2010], ans, id;
struct node {int c, d, v, id;} a[110];
std::vector <int> q[2010];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
bool cmp(node x, node y) {return x.d < y.d;}
/*----------------------------------------函数*/
int main() {
n = read();
for(int i = 1; i <= n; i++) a[i] = (node){read(), read(), read(), i}, m = Max(m, a[i].d);
std::sort(a + 1, a + 1 + n, cmp);
for(int i = 1; i <= n; i++) for(int j = a[i].d; j > a[i].c; j--)
if(f[j - a[i].c] + a[i].v > f[j]) f[j] = f[j - a[i].c] + a[i].v, q[j] = q[j - a[i].c], q[j].push_back(a[i].id);
for(int i = 1; i <= m; i++) if(f[i] > ans) ans = f[i], id = i;
printf("%d\n%d\n", ans, q[id].size());
for(int i = 0; i < q[id].size(); i++) printf("%d ", q[id][i]);
return 0;
}
梦幻岛宝珠
将所有的物品拆分成 \(a \times 2^b\) 的形式 按照 \(b\) 排序 从高到低枚举每一位 \(f_i\) 表示在当前位下 剩余容量为 \(i\) 时的最大价值 然后用普通背包处理着一位的所有物品
相当于将 \(2^b\) 提出来 \(f_i\) 直接弄容量为 \(a\) 转移
考虑如何转移到下一位
\(f_i\) 变成 \(f_{2i + w这一位是否为0}\) 后面的物品最多占用 \(10n\) 的容量 容量再对 \(10n\) 取 \(\min\)
\(b\) 从高位转移 当前位提出一个 \(2^b\) 下一位提出的是 \(2^{b - 1}\) 当前的 \(i\) 相应的也要变成 \(2i\) 而由于提出了 \(2^b\) 也就是说前面只剩下 \(a\) 了 题中限制 \(a\) 是不会超过 \(10\) 的 所以容量最大为 \(10n\) 后面的 \(w\) 是否为 \(1\) 也是转移时提出 \(2^{b - 1}\) 后余下的数也要再加到上面
/*
Time: 3.10
Worker: Blank_space
Source: P3188 [HNOI2007]梦幻岛宝珠
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Abs(x) (x < 0 ? -x : x)
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, m, l, w[40], f[40][2021];
std::vector <int> c[40], v[40];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
/*----------------------------------------函数*/
int main() {
while(1)
{
memset(f, 0, sizeof f); memset(w, 0, sizeof w);
memset(c, 0, sizeof c); memset(v, 0, sizeof v);
n = read(); m = read(); l = 0;
if(!~n && !~m) return 0;
for(int i = 1; i <= n; i++)
{
int x = read(), y = read(), z = 0;
while(!(x & 1)) x >>= 1, z++; l = Max(l, z);
w[z] += x; c[z].push_back(x); v[z].push_back(y);
}
for(int i = 0; i <= l; i++)
for(int j = 0; j < c[i].size(); j++)
for(int k = w[i]; k >= c[i][j]; k--)
f[i][k] = Max(f[i][k], f[i][k - c[i][j]] + v[i][j]);
while(m >> l) l++; l--;
for(int i = 1; i <= l; i++)
{
w[i] += (w[i - 1] + 1) >> 1;
for(int j = w[i]; j >= 0; j--)
for(int k = 0; k <= j; k++)
f[i][j] = Max(f[i][j], f[i][j - k] + f[i - 1][Min(w[i - 1], (k << 1) | ((m >> (i - 1)) & 1))]);
}
printf("%d\n", f[l][1]);
}
return 0;
}
消失之物
预处理一个前缀背包 后缀背包 将两个背包进行组合 然后写挂了
暴力复杂度过高 考虑每次更新不能拿的物品时 其他可以拿的是有重复计算的 考虑保留
采用分治 每次把一个区间分成两部分 一部分不动 另一部分去选不能拿的 由于背包的顺序不会影响答案 处理完分治结构后进行合并 但是并不会写
朴素容斥:
/*
Time: 5.3
Worker: Blank_space
Source: P4141 消失之物
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 10;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, m, f[2021], g[2021], c[2021];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
/*----------------------------------------函数*/
int main() {
n = read(); m = read(); g[0] = 1;
for(int i = 1; i <= n; i++) c[i] = read();
for(int i = 1; i <= n; i++) for(int j = m; j >= c[i]; j--) g[j] = (g[j] + g[j - c[i]]) % mod;
for(int i = 1; i <= n; i++)
{
memcpy(f, g, sizeof g);
for(int j = 1; j <= m; j++) {if(j >= c[i]) f[j] = (f[j] - f[j - c[i]] + mod) % mod; printf("%d", f[j]);}
puts("");
}
return 0;
}
硬币购物
如果没有硬币限制 直接背包 计算一个金额的方案数
对于每一个询问 容斥考虑 枚举至少多少种硬币的数量超过了限制
数量超过限制表示这种硬币至少用了 \(d_i + 1\) 个
假设 \(x\) 为所有枚举出的 钦定超过数量限制的硬币的 \((d_i + 1)c_i\) 的和 那么贡献就是容斥系数乘以 \(f_{s - x}\)
/*
Time: 5.12
Worker: Blank_space
Source: P1450 [HAOI2008]硬币购物
*/
/*--------------------------------------------*/
#include<cstdio>
#define int long long
/*--------------------------------------头文件*/
const int S = 15;
const int B = 1e5 + 7;
/*------------------------------------常量定义*/
int n, c[5], d[5], f[B], ans, a;
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
/*----------------------------------------函数*/
signed main() {
for(int i = 1; i <= 4; i++) c[i] = read(); n = read(); f[0] = 1;
for(int i = 1; i <= 4; i++) for(int j = c[i]; j < B; j++) f[j] += f[j - c[i]];
while(n--)
{
for(int i = 1; i <= 4; i++) d[i] = read(); ans = f[a = read()];
for(int s = S, p = 0, k = 0; s; s = s - 1 & S, k = 0, p = 0)
{
for(int i = 1, j = 1; i <= 4; i++, j <<= 1) if(j & s)
k += c[i] * (d[i] + 1), p ^= 1;
if(a >= k) ans += p ? -f[a - k] : f[a - k];
}
printf("%lld\n", ans);
}
return 0;
}
宝石镶嵌
给定 \(n\) 个数 取掉其中 \(k\) 个 使得剩余的数按位或的结果最大
上限为 \(10^5\) 只有 \(17\) 二进制位
如果 \(n - k \geq 17\) 每一位都选择一个对应位为 \(1\) 的数
考虑 \(n - k < 17\) 的情况 即 \(n < 117\)
直接背包 \(f_i\) 表示或 \(= i\) 时
//搬代码
for(int i = 1; i <= n; i++)
for(int j = 1e5; j >= 0; j--)
f[i + 1][j | w[i]] = Min(f[i + 1][j | w[i]], f[i][j] + 1);
for(int i = 1e5; i >= 0; i--)
if(f[n + 1][i] <= n - k) return printf("%d\n", i), 0;
奇怪的背包
弃
$ ——\ End$