Loading

P9871 [NOIP2023] 天天爱打卡 (离散化+线段树优化dp)

P9871 [NOIP2023] 天天爱打卡

upd 2024.11.13

经典dp+线段树优化+离散化

可以看到求的是最大值,常用工具有 dp 和贪心。如果 dp 做的多的话应该可以看出这题就是个线性 dp(先忽略超大的数据范围)。

\(n\) 小的时候,可以直接以当前天数设状态,转移就是一段后缀天数跑步,贡献可以在线预处理。

\(O(n^2)\) 的复杂度啊,怎么优化?状态已经很简洁,可以想想拆式子,然后通过数据结构优化这方面。

发现可以用线段树优化了!然后复杂度是 \(O(n\log n)\) 了,但还是不够啊。

这时候你应该要注意到许多无效的转移位置(比如一段很长的跑步中间的部分),这些位置并不会改变贡献,怎么避免它?离散化。我们只关心端点附近的信息。于是复杂度就是 \(O(m\log m)\)

前两个步骤略讲,主要是离散化。

首先考虑dp,朴素的,容易写出状态 \(f_i\) 表示考虑到第 \(i\) 个位置,且强制第 \(i\) 天跑步的最大能量值。

考虑枚举最后一段跑步的时间,有

\(f_i=\max(\max\limits_{k<j}f_k-(i-j)\times d+\sum\limits_{j<l_p\le r_p\le i}v_p)\)

不妨设 \(g_i=\max\limits_{j<i}f_i\),再自然的参变分离,有

\(f_i=\max(g_j+j\times d+\sum\limits_{j<l_p\le r_p\le i}v_p)-i\times d\)

容易看出只需要维护前面的最大值即可。考虑线段树优化。对于 \(g_j+j\times d\) 是定值,直接在线段树末尾插入即可。后面的部分可以在枚举的过程中不断把 \(r_p=i\) 的线段加入,也就是线段树上区间加。具体的说,对于每一个 \([l_i,r_i]\),给所有 \(j<l_i\) 加入 \(v_i\),表示若走满 \([j+1,i]\) 可以取到该线段。

这就是前面的部分,复杂度为 \(O(n\log n)\)

考虑优化。首先数据范围显然想让我们把复杂度降到 \(O(m\log m)\)。于是我们就观察到 \(f\) 序列会分成很多段。

这些线段的左右端点把整个序列分为多段,观察每一段的决策,对于 \([l,r]\) 段,一定只有都跑和都不跑两种情况。考虑选每段的右端点作为该段的代表元。于是对于每个线段,我们只保留 \(l_i-1\)\(r_i\) 两个端点即可,离散化完之后转移同样。

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

#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;
}
posted @ 2024-03-23 20:25  Fire_Raku  阅读(121)  评论(0编辑  收藏  举报