1 条题解
-
0
我们从最简单的方式进行考虑。
我们定义 表示在打第 关,用了 次第 把武器后,还剩 的金币数,是否可以从边界情况转移过来。
这个边界情况就是 。
那么肯定会从 转移过来。同时,还需要枚举上一把可能的剑是什么,以及用了多少次,将这些的 dp 的值或起来就行。需要注意的是,在枚举的同时还需要判断剑是否能杀死怪物。
当然,这个的时间复杂度为 ,可以得到样例分。
一种最经典的降维方式就是把金币数量变为 dp 的答案。
我们可以这样定义, 表示在打第 关,用了 次第 把武器后,还剩的金币数量最多的是 。
这样,就可以将上面的或运算转化为 max 运算。
现在优化掉了 ,但是还是只能的样例分。
肯定是要降维的,所以直接考虑将第 维优化掉。
这时候,就不能只靠上一个 转移了,需要的是 之间的数进行转移。
转移方程大致如下,后面还有一些约束条件:
$$dp_{i, j} = \max_{\max(i-k, 0) \le t < i, 0 \le p \le m} dp_{t, p} + s_i - s_t - c_j$$这个 表示的是 的前缀和。
然后, 还需要满足 ,而 需要满足 时 也必须为 。
具体代码如下:
for (int i = 1;i<= n;i++) { for (int j = 1;j<= m;j++) { for (int t = max(i-k, 0ll);t< i;t++) { if (doQueryMax(1, 1, n, t+1, i) > c[j].a) continue; // 线段树实现查询区间最大 for (int p = 0;p<= m;p++) { if (p == 0 && t != 0) continue; if (dp[t][p] < c[j].b) continue; dp[i][j] = max(dp[i][j], dp[t][p] + psum[i] - psum[t] - c[j].b); } } } }考虑继续降维,我们可以清晰的发现 没有被转移来的 dp 所用,只用在一个定值上。
所以就可以得出 表示打完第 轮后,最多有多少金币。
转移方程大致如下,边界跟上面的差不多:
其中 表示在区间 中,怪兽的生命值最大为 ,找到一把剑使得 ,且需要的金币最少。
这样 的算法就出来了,可以成功的得到 分。
现在也降不了维了,只能考虑优化时间复杂度。
考虑使用线段树维护,但是 好像并不好直接维护,每个位置的 都是在变的。同时,我们还需要满足 这一条件。
但是呢,我们发现这是一个单调不递减的东西,而每次加了一个值,可能会覆盖前面的某些值来满足单调性。
这个就很像单调栈了,每个元素记录管辖区间,生命值,和 的值,进行修改即可。
这个地方就可以使用平衡树或势能线段树来做了。
理论上,最坏的时间复杂度应该是 。
写代码的时候,特别是写单调栈部分,多多注意一些情况。
可能还有些问题:
如果存在一个位置不满足 ,经过后面的添加,可能会满足条件吗?
不会,因为这个是一个单调的东西,添加一个数,要么不变,要么变大。
思路在于解释,实现在于代码,如果不懂如何写势能线段树,可以看一下代码。
#include <bits/stdc++.h> #define int long long using namespace std; const int N = 5e5+10; const int inf = 0x3f3f3f3f3f3f3f3f; struct node { int a, b; } c[N]; struct Tuple { int first, second, third; }; int n, m, k, x; int a[N], b[N]; int dm; int d[N*2]; int psum[N]; int dp[N]; int top; Tuple st[N]; // 第一维就是怪兽的生命值,第二维是这个所覆盖区间的左端点,第三维是 f 函数的值 void read(int &x) { int f = 1; x = 0; char ch = getchar_unlocked(); while (!(ch >= '0' && ch <= '9')) { if (ch == '-') f = -1; ch = getchar_unlocked(); } while (ch >= '0' && ch <= '9') { x = x * 10 + (ch - '0'); ch = getchar_unlocked(); } x *= f; } class Beats { // 势能线段树 /* 我们记录 ma 和 mi 当我们减掉 f 函数值是就需要判断 mi - f 是否小于 0,这里实现的是 doChage 部分 小于 0 就可以还原了 有些大于 0 的,是通过判断排除的,详见 del 因为 doChange 部分有判断 mi-f 的,所以只能再写一个单修的了 */ private: int tag[4*N], ma[4*N], mi[4*N]; public: void del(int k, int l, int r) { if (mi[k] >= 0 || ma[k] < -inf / 2) return ; // 保证时间复杂度正确 if (l == r) { // 还原 ma[k] = -inf; mi[k] = inf; return ; } pushDown(k); int mid = (l + r) >> 1; del(k*2, l, mid), del(k*2+1, mid+1, r); ma[k] = max(ma[k*2], ma[k*2+1]); mi[k] = min(mi[k*2], mi[k*2+1]); } void doChangeOne(int k, int dx) { tag[k] += dx, ma[k] += dx, mi[k] += dx; } void pushDown(int k) { doChangeOne(k*2, tag[k]); doChangeOne(k*2+1, tag[k]); tag[k] = 0; } void doChange(int k, int l, int r, int x, int y, int dx) { if (r < x || y < l) return ; if (x <= l && r <= y) { doChangeOne(k, dx); if (mi[k] < 0) del(k, l, r); return ; } pushDown(k); int mid = (l + r) >> 1; doChange(k*2, l, mid, x, y, dx); doChange(k*2+1, mid+1, r, x, y, dx); ma[k] = max(ma[k*2], ma[k*2+1]); mi[k] = min(mi[k*2], mi[k*2+1]); } void doChangePoint(int k, int l, int r, int x, int dx, int dy) { if (r < x || x < l) return ; if (l == r) { ma[k] = dx, mi[k] = dy; tag[k] = 0; return ; } pushDown(k); int mid = (l + r) >> 1; doChangePoint(k*2, l, mid, x, dx, dy); doChangePoint(k*2+1, mid+1, r, x, dx, dy); ma[k] = max(ma[k*2], ma[k*2+1]); mi[k] = min(mi[k*2], mi[k*2+1]); } int doQueryMax(int k, int l, int r, int x, int y) { if (r < x || y < l || x > y) return -inf; if (x <= l && r <= y) return ma[k]; pushDown(k); int mid = (l + r) >> 1; return max(doQueryMax(k*2, l, mid, x, y), doQueryMax(k*2+1, mid+1, r, x, y)); } } yjmiao; class soilder { private: int mi[4*N]; public: void doBuild(int k, int l, int r) { if (l == r) { mi[k] = c[l].b; return ; } int mid = (l + r) >> 1; doBuild(k*2, l, mid); doBuild(k*2+1, mid+1, r); mi[k] = min(mi[k*2], mi[k*2+1]); } int doQueryMin(int k, int l, int r, int x, int y) { if (r < x || y < l || x > y) return inf; if (x <= l && r <= y) return mi[k]; int mid = (l + r) >> 1; return min(doQueryMin(k*2, l, mid, x, y), doQueryMin(k*2+1, mid+1, r, x, y)); } } zpmiao; __inline bool cmp(node x, node y) { return x.a < y.a; } __inline int lower(int l, int r, int x) { int ans = r + 1; while (l <= r) { int mid = (l + r) >> 1; if (c[mid].a >= x) { ans = mid, r = mid - 1; } else l = mid + 1; } return ans; } __inline void solve() { dp[0] = x; yjmiao.doChangePoint(1, 0, n, 0, x, x); // 栈的定义见上方定义数组处 for (int i = 1;i<= n;i++) { st[++top] = {0, i-1, 0}; // 一定要有这个~ 原来的栈右端点是 i-2 没有 i-1 所以需要新加一个 int last = i-1; while (top > 0 && st[top].first <= a[i]) { yjmiao.doChange(1, 0, n, st[top].second, last, st[top].third); // 还原,原来是减法嘛 现在还原就是用加法呗~ last = st[top].second-1; top--; } int id = lower(1, m, a[i]); int d = zpmiao.doQueryMin(1, 1, m, id, m); st[++top] = {a[i], last+1, d}; yjmiao.doChange(1, 0, n, st[top].second, i-1, -d); // 重新覆盖啦~ dp[i] = yjmiao.doQueryMax(1, 0, n, max(i-k, 0ll), i-1) + psum[i]; yjmiao.doChangePoint(1, 0, n, i, dp[i] - psum[i], dp[i]); } if (dp[n] >= 0) printf("Yes\n"); else printf("No\n"); return ; } signed main() { int Max = -1; read(n), read(m), read(k), read(x); for (int i = 1;i<= n;i++) { read(a[i]), read(b[i]); d[++dm] = a[i]; psum[i] = psum[i-1] + b[i]; } for (int i = 1;i<= m;i++) { read(c[i].a), read(c[i].b); d[++dm] = c[i].a; } // 将 h 和 s 数组离散化 sort(d+1, d+dm+1); dm = unique(d+1, d+dm+1)-d-1; for (int i = 1;i<= n;i++) { a[i] = lower_bound(d+1, d+dm+1, a[i])-d; Max = max(Max, a[i]); } for (int i = 1;i<= m;i++) { c[i].a = lower_bound(d+1, d+dm+1, c[i].a)-d; } sort(c+1, c+m+1, cmp); zpmiao.doBuild(1, 1, m); if (Max > c[m].a) { printf("No\n"); return 0; } solve(); return 0; }略微卡常即可。
- 1
信息
- ID
- 12581
- 时间
- 1000ms
- 内存
- 700MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者