2022.6.30 DP 题三道

Tips

  1. 有维度观念,注意这道题如果要 DP 有哪些维度。维度不一定要在状态中,还可以在条件或DP值中。
  2. 对子问题的理解。
  3. 观察性质 -> 建立、优化状态
  4. 全部 <-> 前缀
  5. 分类讨论

「JOISC 2020 Day4」治疗计划
本题 dp 的设定和转移都很 NB!

本题的维度有时间,坐标。

按时间 DP 似乎是不错的决定,但根本无法转移。

把坐标加入状态?不行,太多了。

全部覆盖完?这让我们联想到前缀。把前缀搞为状态。也是很多。我最初的想法是按 \(R\) 排序,\(dp_i\) 为覆盖完 \([1,R_i]\) 最多需要多少治疗方案。

然后我们可以推出 \(dp_i=cost_i+\min\{dp_j\}(R_j-L_i+1\geqslant |T_i-T_j|)\)

然后我们考虑 \(j\) 的范围,不容易地看出,\(\{j\}\neq \{x|1\leqslant x < i\}\)。不一定按照 \(R\) 从小到大转移(因为可以理解成范围会随着时间衰减)。但如果我们将右端点加上 \(T\) 也不对,因为不一定会来到那个时刻。所以如果我们将 \(j-i\) 连边,他会是一个复杂的图。

这种时候可以考虑 Dijkstra 的思想,因为 \(dp_j\) 转移过来没有系数,且没有负的价值。所以 \(dp_i\) 只会被更新一次。但边集也是 \(n^2\),也就是说不能遍历所有边。但是,遍历到一个点后,它连接的所有边就会失效


CF1523F Favorite Game
一道很妙的 DP。

考虑 Tip 1,本题的维度有传送门激活的状态,时间,完成任务数量,当前的位置。

观察数据范围可以列出状态 \(dp_{S,x,c}\),表示传送门激活状态为 \(S\),当前在 \(x\),完成任务数量为 \(c\) 的最小时间。口胡可以 \(\mathcal {O}(m)\) 转移。现在的时间复杂度为 \(\mathcal {O}(2^nm^3)\),观察数据范围我们应该会需要 \(\mathcal {O}(2^nm^2)\) 的复杂度。

此时用到 Tip 3,注意到:

  1. 如果我们在任务的位置,不用考虑时间。
  2. 如果我们在传送门,不用考虑位置。

所以,令 \(f_{S,x}\) 为当前传送门状态为 \(S\),在第 \(x\) 个任务位置所完成的最大任务数,\(g_{S,x}\) 为当前传送门状态为 \(S\),完成了 \(x\) 个任务所需的最小时间。分类讨论 推出转移方程:
(p.s. 令 \(\bigoplus\) 为加入一个元素到一个集合)
传送门 -> 传送门:\(g_{S\bigoplus i,x}=g_{S,x}+dis_{S,i}\)
传送门 -> 任务地点:\(f_{S,x}=i+1,\ g_{S,i}+dis_{S,x}\leqslant T_x\)
任务地点 -> 传送门:\(g_{S\bigoplus i,f_{S,x}}=T_x+\min(dis_{x,i},dis_{S,i})\)
任务地点 -> 任务地点:\(f_{S,x}=f_{S,y}+1,\ T_y+\min(dis_{x,y},dis_{S,x})\leqslant T_x\)
这里是先更新 \(f\),再更新 \(g\)。因为更新 \(g\) 需要用到 \(f\) 的最佳状态。
注意 DP 边界!!!

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define LL long long
#define uint unsigned int
using namespace std;
const int MAXN = 20, MAXM = 105;
struct node {
	int x_, y_, T;
	bool operator < (const node P) const { return T < P.T; }
}arr[MAXM + MAXN];
int n, m, X[MAXN + MAXM], Y[MAXM + MAXN], f[(1 << 15) + 5][MAXN + MAXM];
LL g[(1 << 15) + 5][MAXN + MAXM], dis[(1 << 15) + 5][MAXN + MAXM];
int dp[MAXN + MAXM][MAXN + MAXM], ans;
int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i ++) scanf("%d%d", &X[i], &Y[i]), arr[m + i].x_ = X[i], arr[m + i].y_ = Y[i];
	for(int i = 1; i <= m; i ++) scanf("%d%d%d", &arr[i].x_, &arr[i].y_, &arr[i].T);
	sort(arr + 1, arr + 1 + m);
	for(int i = 1; i <= n + m; i ++) for(int j = 1; j <= n + m; j ++) dp[i][j] = abs(arr[i].x_ - arr[j].x_) + abs(arr[i].y_ - arr[j].y_);
//	for(int k = 1; k <= n + m; k ++) for(int i = 1; i <= n + m; i ++) for(int j = 1; j <= n + m; j ++) dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
	for(int i = 0; i < (1 << n); i ++) {
		for(int j = 1; j <= n + m; j ++) {
			LL mn = 1e18;
			for(int k = 0; k < n; k ++) if((i >> k) & 1) mn = min(mn, 1ll * dp[m + 1 + k][j]);
			dis[i][j] = mn;
		}
	}
	for(int i = 0; i < (1 << n); i ++) for(int j = 0; j <= m; j ++) g[i][j] = 1e18;
	g[0][0] = 0;
	for(int i = 0; i < n; i ++) g[1 << i][0] = 0;
	for(int i = 0; i < (1 << n); i ++) for(int j = 1; j <= m; j ++) f[i][j] = -0x3f3f3f3f;
	for(int j = 1; j <= m; j ++) f[0][j] = 1;
	for(int i = 0; i < (1 << n); i ++) {
		for(int j = 1; j <= m; j ++) { // f	
			for(int k = 0; k <= m; k ++) {
				if(g[i][k] + dis[i][j] <= arr[j].T) f[i][j] = max(f[i][j], k + 1);
				if(k < j && k && arr[k].T + min(dp[j][k] * 1ll, dis[i][j]) <= arr[j].T) f[i][j] = max(f[i][j], f[i][k] + 1);
				ans = max(ans, f[i][j]);
			}
		}
		for(int j = 0; j <= m; j ++) {
			for(int k = 0; k < n; k ++) {
				if((i >> k) & 1) continue;
				g[i | (1 << k)][j] = min(g[i | (1 << k)][j], g[i][j] + dis[i][k + m + 1]);
				if(j && f[i][j] >= 0) g[i | (1 << k)][f[i][j]] = min(g[i | (1 << k)][f[i][j]], 1ll * arr[j].T + min(1ll * dp[j][k + 1 + m], dis[i][k + 1 + m]));
			}
		}
	}
	printf("%d", ans);
	return 0;
}

posted @ 2022-07-01 20:10  Saintex  阅读(39)  评论(0编辑  收藏  举报