// // // // // // // // // // // // // //

$DP$ 优化

\(DP\) 优化

  • 数据结构

单调队列 线段树 树状数组

  • 决策单调性优化

  • 凸优化

  • 四边形不等式优化

  • 矩阵快速幂优化

鹰蛋问题

给定 \(n\) 个一模一样的蛋 从 \(x\) 以上的层扔下去会碎 采取最优策略 问最坏扔多少次知道蛋从第几层扔下去刚好会碎

\(f_{i, j}\) 表示 \(i\) 个蛋 测 \(j\) 层楼的最少次数

转移考虑从 \(k\) 层扔一个蛋 碎不碎

\(f_{i, j} = \min\{\max\{f_{i - 1, k - 1}, f_{i, j - k}\} + 1\}\)

复杂度 \(O(m^3)\)

蛋的个数大于 \(\log m + 1\) 直接二分

\(dp\) 的第一维缩小到 \(\log m\) 的级别

复杂度 \(O(m^2 \log m)\)

具有决策单调性(?)

随着 \(k\) 的增大 后面那一项是单调递减的 前面那一项是单调增大的

当有一个位置 \(k_0\) 满足 \(f_{i - 1, k_0 - 1} > f_{i, j - k_0}\)

...

另一种思路

\(f_{i, j}\) 表示用 \(i\) 个蛋 扔 \(j\) 次 最多可以试出多少层

转移时同样考虑蛋碎不碎

\(f_{i, j} = f_{i - 1, j - 1} + f_{i, j - 1} + 1\)

\(f_{n, j}\) 中进行二分查找 找给出的 \(m\) 找到给出的 \(j\) 就是要尝试的次数 其中 \(i\)\(\log m\) 级别 考虑 \(j\) 的范围

...

单调队列

\(Trade\)

\(T\) 天 第 \(i\) 天买股票话 \(Ap_i\) 元 买股票花 \(Bp_i\) 最多能买 \(As_i\) 股 最多能卖 \(Bs_i\) 股 任何时候股票持有量不超过 \(MaxP\) 且两个交易日至少间隔 \(w\) 天 求 \(T\) 天后的最大收益

\(f_{i, j}\) 表示 第 \(i\) 天 持有 \(j\) 股票时的最大收益

\[f_{i, j} = \begin{cases} f_{i - 1, j}\\ f_{i - w - 1, k} - (j - k) \times Ap_i\\ f_{i - W - 1, k} - (k - j) \times Bp_i \end{cases} \]

套单调队列优化

/*
  Time: 5.5
  Worker: Blank_space
  Source: P2569 [SCOI2010]股票交易
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define Max(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, maxp, W, f[2021][2021], q[A], l, r, 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 main() {
	n = read(); maxp = read(); W = read(); memset(f, 128, sizeof f);
	for(int i = 1; i <= n; i++)
	{
		int AP = read(), BP = read(), AS = read(), BS = read();
		for(int j = 0; j <= AS; j++) f[i][j] = -j * AP;
		for(int j = 0; j <= maxp; j++) f[i][j] = Max(f[i][j], f[i - 1][j]);
		if(i <= W) continue;
		l = 1; r = 0;
		for(int j = 0; j <= maxp; j++)
		{
			while(l <= r && f[i - W - 1][q[r]] + q[r] * AP <= f[i - W - 1][j] + j * AP) r--;
			q[++r] = j;
			while(l <= r && j - q[l] > AS) l++;
			if(l <= r) f[i][j] = Max(f[i][j], f[i - W - 1][q[l]] - (j - q[l]) * AP);
		}
		l = 1; r = 0;
		for(int j = maxp; j >= 0; j--)
		{
			while(l <= r && f[i - W - 1][q[r]] + q[r] * BP <= f[i - W - 1][j] + j * BP) r--;
			q[++r] = j;
			while(l <= r && q[l] - j > BS) l++;
			if(l <= r) f[i][j] = Max(f[i][j], f[i - W - 1][q[l]] + (q[l] - j) * BP);
		}
	}
	for(int j = 0; j <= maxp; j++) ans = Max(ans, f[n][j]);
	printf("%d", ans);
	return 0;
}

跳石头

要求两名玩家排成一排的 \(n\) 个石头上跳跃 每个石头给定高度 只能向右跳 跳过的时候会消失 两个人不能站在同一个石头上 游戏分数为两个玩家战国的石头的总数 任选起点 求最高分数

\(f_{i, j}\) 表示一个人在 \(i\) 另一个人在 \(j\) 的最大分数

\[f_{i, j} = \begin{cases} \max\{f_{u, j}\} + 1 & a_i > a_u\\ \max\{f_{i, v}\} + 1 & a_j > a_v \end{cases} \]

复杂度 \(O(n^3)\)

树状数组维护最大值

for(int i = 1; i <= n; i++)
    for(int j = 1; j <= n; j++) if(i != j)
    {
        f[i][j] = Max(f[i][j], bit1.sum(a[i]));
        f[i][j] = Max(f[i][j], bit2.sum(a[i]));
        bit1.add(a[i], f[i][j]); bit2.add(a[j], f[i][j]);
        ans = Max(ans, f[i][j]);
    }

题目

将一个长度为 \(n\) 的非负序列划分成若干段 每段数字和不超过 \(m\) 求每段最大值和最小的划分方法 输出最小的和

\(M(i, j)\) 表示区间 \([i, j]\) 的元素最大值

\(f_i = \min\{f_j, M(j + 1, i) \}\)

发现 \(f_i\) 单调不减 考虑选取什么位置转移

维护递减的队列 存的是符合要求的某一段的最大值 注意这个问题不是可以通过单调队列优化的 需要通过线段树的区间查询最大值优化 \(dp\)

其他

不可做

读入 然后对原数组建树 建递减的单调队列 单调队列 \(1\) 位置入队 外层循环枚举 \(i\) \(a_i\) 入队 找到队列中前一个数 位置为 \(j\) 将区间 \([j + 1, i]\) 加上 \(a_i\)

蒙了

\(Single\ Elimination\)

\(16\) 名选手参加淘汰赛 (\(16 \to 8 \to 4 \to 2 \to 1\) )给定两人之间的比赛 知道哪一位一定赢 胜负关系可能出现环 对于每一位选手 询问是否可以通过设计第一轮匹配使得该选手夺冠

...

\(1D/1D\) \(DP\) 优化

状态数 \(O(n)\) 每个状态的决策量 \(O(n)\)\(DP\) 朴素做法一般为 \(O(n^2)\)

\(Tobotruck\)

一个机器人从原点出发 按订单顺序将若干个物品送到终点 第 \(i\) 个订单要求将 \(m_i\) 个物品送到 \((x_i, y_i)\) 机器人一次最多持有 \(c\) 个物品 但可以回原点补充 机器人只能沿平行于最标轴方向行走 问送完所有物品并回到原点的最小路程

直观的 \(dp\)\(f_i\) 表示送完前 \(i\) 个订单并返回终点的答案 设 \(d_i\) 表示 \((x_i, y_i)\) 到原点的距离

\(f_i = \min\{f_j + d_i + d_j + dis(j + 1, ..., i) \}\)

条件 \(m_{j + 1} + ... + m_i \leq c\)

如果将 \(dis(a, ... , b)\) 改为 \(s_b - s_{a - 1}\)

\(f_i = d_i + s_i + \min\{f_i + d_{j + 1} - s_j \}\)

套单调队列

/*
  Time: 5.5
  Worker: Blank_space
  Source: UVA1169 Robotruck
*/
/*--------------------------------------------*/
#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 T, n, f[B], q[B], l, r, c, d[B], dis[B], w[B], _x, _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;
}
/*----------------------------------------快读*/
void work() {
	memset(f, 63, sizeof f); w[0] = dis[0] = d[0] = _x = _y = 0;
	c = read(); n = read(); f[0] = 0; l = r = 1; q[1] = 0;
	for(int i = 1; i <= n; i++) 
	{
		int x = read(), y = read(); w[i] = read() + w[i - 1]; d[i] = x + y;
		dis[i] = Abs(x - _x) + Abs(y - _y) + dis[i - 1];
		_x = x; _y = y;
	}
	for(int i = 1; i <= n; i++)
	{
		while(l <= r && w[i] - w[q[l]] > c) l++;
		f[i] = f[q[l]] + d[i] + d[q[l] + 1] + dis[i] - dis[q[l] + 1];
		while(l <= r && f[q[r]] + d[q[r] + 1] - dis[q[r] + 1] >= f[i] + d[i + 1] - dis[i + 1]) r--;
		q[++r] = i;
	}
	printf("%d\n", f[n]);
	if(T) puts("");
}
/*----------------------------------------函数*/
int main() {
	T = read(); while(T--) work();
	return 0;
}

玩具装箱

\[f_i = \min\{f_j + (i - j - 1 - L + s_i - s_j)^2 \} \]

代码

/*
  Time: 3.24
  Worker: Blank_space
  Source: P3195 [HNOI2008]玩具装箱
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define int long long
#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, L, f[B], l, r, q[B], sum[B];
/*------------------------------------变量定义*/
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 T(int i) {return 2 * (i + sum[i] - 1 - L);}
double K(int x, int y) {return (f[x] - f[y] + (x + sum[x]) * (x + sum[x]) - (y + sum[y]) * (y + sum[y])) * 1.0 / (x - y + sum[x] - sum[y]) * 1.0;}
/*----------------------------------------函数*/
signed main() {
	n = read(); L = read();
	for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] + read();
	for(int i = 1; i <= n; i++)
	{
		while(l < r && K(q[l], q[l + 1]) < T(i)) l++;
		f[i] = f[q[l]] + (i - q[l] - 1 + sum[i] - sum[q[l]] - L) * (i - q[l] - 1 + sum[i] - sum[q[l]] - L);
		while(l < r && K(q[r - 1], q[r]) > K(q[r], i)) r--;
		q[++r] = i;
	}
	printf("%lld", f[n]);
	return 0;
}

斜率优化

\(Land\ Acquisition\ G\)

排序 + 斜率优化

代码

/*
  Time: 3.26
  Worker: Blank_space
  Source: P2900 [USACO08MAR]Land Acquisition G
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
#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, n, w[C], L[C], f[C], q[C], l, r;
struct node {int w, l;}a[C];
/*------------------------------------变量定义*/
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.l == y.l ? x.w > y.w : x.l > y.l;}
double K(int x, int y) {return (f[x] - f[y]) * 1.0 / (L[y + 1] - L[x + 1]) * 1.0;}
/*----------------------------------------函数*/
signed main() {
	_n = read(); for(int i = 1; i <= _n; i++) a[i].w = read(), a[i].l = read();
	std::sort(a + 1, a + 1 + _n, cmp);
	for(int i = 1; i<= _n; i++) if(a[i].w > w[n]) w[++n] = a[i].w, L[n] = a[i].l;
	for(int i = 1; i <= n; i++)
	{
		while(l < r && K(q[l], q[l + 1]) < w[i]) l++;
		f[i] = f[q[l]] + L[q[l] + 1] * w[i];
		while(l < r && K(q[r - 1], q[r]) > K(q[r], i)) r--;
		q[++r] = i;
	}
	printf("%lld", f[n]);
	return 0;
}

其他题目

  • 仓库建设
  • 锯木厂选址
  • 序列分割
  • 购票
  • 国王饮水记
  • 防御准备
  • \(p\) 的农场
  • 保护出题人

决策单调性



题目

  • 诗人小 \(G\)
  • 柠檬

\(——\ End\)

posted @ 2021-05-05 20:22  Blank_space  阅读(49)  评论(2编辑  收藏  举报
// // // // // // //