P9871 [NOIP2023] 天天爱打卡 (离散化+线段树优化dp)
upd 2024.11.13
经典dp+线段树优化+离散化
可以看到求的是最大值,常用工具有 dp 和贪心。如果 dp 做的多的话应该可以看出这题就是个线性 dp(先忽略超大的数据范围)。
小的时候,可以直接以当前天数设状态,转移就是一段后缀天数跑步,贡献可以在线预处理。
的复杂度啊,怎么优化?状态已经很简洁,可以想想拆式子,然后通过数据结构优化这方面。 发现可以用线段树优化了!然后复杂度是
了,但还是不够啊。 这时候你应该要注意到许多无效的转移位置(比如一段很长的跑步中间的部分),这些位置并不会改变贡献,怎么避免它?离散化。我们只关心端点附近的信息。于是复杂度就是
。
前两个步骤略讲,主要是离散化。
首先考虑dp,朴素的,容易写出状态
考虑枚举最后一段跑步的时间,有
不妨设
容易看出只需要维护前面的最大值即可。考虑线段树优化。对于
这就是前面的部分,复杂度为
考虑优化。首先数据范围显然想让我们把复杂度降到
这些线段的左右端点把整个序列分为多段,观察每一段的决策,对于
复杂度
#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back
typedef long long i64;
const int N = 1e5 + 10;
i64 n, m, k, d, tot;
struct node {
i64 l, r, v;
} a[N];
i64 b[N << 1], f[N << 1];
bool cmp(node a, node b) {
return a.r < b.r;
}
struct seg {
i64 v, lzy;
} t[(N << 1) << 2];
void pushup(int u) {t[u].v = std::max(t[u << 1].v, t[u << 1 | 1].v);}
void build(int u, int l, int r) {
t[u].lzy = 0;
if(l == r) {
t[u].v = (l ? -1e18 : 0);
return;
}
int mid = (l + r) >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
void pd(int u) {
if(!t[u].lzy) return;
i64 tu = t[u].lzy;
t[u << 1].v += tu, t[u << 1].lzy += tu;
t[u << 1 | 1].v += tu, t[u << 1 | 1].lzy += tu;
t[u].lzy = 0;
}
i64 query(int u, int l, int r, int L, int R) {
if(L <= l && r <= R) {
return t[u].v;
}
int mid = (l + r) >> 1;
pd(u);
i64 ans = -1e18;
if(L <= mid) ans = std::max(ans, query(u << 1, l, mid, L, R));
if(R > mid) ans = std::max(ans, query(u << 1 | 1, mid + 1, r, L, R));
return ans;
}
void ins(int u, int l, int r, int L, i64 x) {
if(l == r) {
t[u].v = x;
return;
}
int mid = (l + r) >> 1;
pd(u);
if(L <= mid) ins(u << 1, l, mid, L, x);
else ins(u << 1 | 1, mid + 1, r, L, x);
pushup(u);
}
void update(int u, int l, int r, int L, int R, i64 x) {
if(L <= l && r <= R) {
t[u].v += x,t[u].lzy += x;
return;
}
int mid = (l + r) >> 1;
pd(u);
if(L <= mid) update(u << 1, l, mid, L, R, x);
if(R > mid) update(u << 1 | 1, mid + 1, r, L, R, x);
pushup(u);
}
void Solve() {
std::cin >> n >> m >> k >> d;
tot = 0;
for(int i = 1; i <= m; i++) {
std::cin >> a[i].r >> a[i].l >> a[i].v;
a[i].l = a[i].r - a[i].l;
b[++tot] = a[i].l, b[++tot] = a[i].r;
}
std::sort(b + 1, b + tot + 1);
std::sort(a + 1, a + m + 1, cmp);
tot = std::unique(b + 1, b + tot + 1) - b - 1;
for(int i = 1; i <= m; i++) {
a[i].l = std::lower_bound(b + 1, b + tot + 1, a[i].l) - b;
a[i].r = std::lower_bound(b + 1, b + tot + 1, a[i].r) - b;
}
build(1, 0, tot);
i64 g = 0;
for(int i = 1, lst = 0, nw = 1; i <= tot; i++) {
ins(1, 0, tot, i, g + d * b[i]);
for(; nw <= m && a[nw].r == i; nw++) {
update(1, 0, tot, 0, a[nw].l, a[nw].v);
}
for(; lst < i && b[lst] < b[i] - k; lst++);
if(lst < i) f[i] = query(1, 0, tot, lst, i - 1) - d * b[i];
else f[i] = -1e18;
// std::cout << f[i] << " \n"[i == tot];
g = std::max(g, f[i]);
}
std::cout << g << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int c, t;
std::cin >> c >> t;
while(t--) Solve();
return 0;
}
Buy me a cup of coffee ☕.
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析