1 条题解
-
0
自动搬运
来自洛谷,原作者为

kyel
**搬运于
2025-08-24 21:35:35,当前版本为作者最后更新于2019-04-14 00:27:52,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
标签:线段树,单调队列,动态规划。
令front[i]表示最近的不能和i共区间的元素位置,显然front[i]可以在输入时通过取最大值处理出来。显然,i不能和front[i]以及位置更靠前的元素划为一段。
令dp[i]表示使区间[1, i]中的元素合法分段所需要的最小代价
考虑向位置i转移,假设有位置j在i前且合法。则要么dp[i] = dp[i - 1] + k,即元素i单独分一段,要么找到某个值j使得dp[j] + cost(j + 1, i)最小。其中cost(j + 1, i) = s * (Max(j + 1, i) - Min(j + 1, i)),即将区间[j + 1, i]分为一段的代价。
显然,重点在于如何高效地找到j使得dp[j] + cost(j + 1, i)最小。
用线段树维护。线段树维护的是从之前某一位置j进行转移且将位置j与当前位置划为一段的最小代价,当然使用时是区间查询而不关心具体位置。假定我们已经求得了dp[i],且将区间[j, i - 1]划为一段的代价记为V0,那么我们发现,如果有位置j < i且Vj为Min(j + 1, i - 1)且Vi < Vj,即在位置j之后除了Vi没有比Vj小的值,那么如果位置i+1希望从j位置或者更靠前一些的位置转移,则代价为V0 + s * (Vj - Vi),因为Vi取代了Vj最小值的地位,需要补上增加的代价。为什么说是更靠前“一些”呢?因为位置j之前可能有比Vj还小的元素,而这个时候如果有Vk > Vi,那么对应需要增加的代价就是s * (Vk - Vi)。因此,这些位置是从后往前值递减的,因为如果出现了位置j > k且Vj < Vk,那么k不可能是之后计算代价用到的最小值,我们就不再关心了。因此,可以用一个从前往后单调递增的单调队列Min来维护这个位置的序列,然后用线段树做区间加法维护最小代价。
最大值同理利用单调队列维护。
显然,文字叙述过于繁琐,更简明的思路请参照代码。
#include <cstdio> #include <cstdlib> #include <deque> #include <algorithm> namespace my { template <class T> inline void getmax(T& a, T b) { if (b > a) a = b; } template <class T> inline void getmin(T& a, T b) { if (b < a) a = b; } } const int maxn(112345); namespace seg { long long val[maxn << 2], tag[maxn << 2]; #define ls (n << 1) #define rs (n << 1 | 1) inline void push(int n) { if (tag[n]) { val[ls] += tag[n], tag[ls] += tag[n]; val[rs] += tag[n], tag[rs] += tag[n]; tag[n] = 0; } } inline void update(int n) { val[n] = std::min(val[ls], val[rs]); } long long quary(int n, int left, int right, int l, int r) { if (left == l && right == r) { return val[n]; } push(n); int mid(left + right >> 1); if (r <= mid) return quary(ls, left, mid, l, r); else if (l > mid) return quary(rs, mid + 1, right, l, r); return std::min(quary(ls, left, mid, l, mid), quary(rs, mid + 1, right, mid + 1, r)); } void modify(int n,int left, int right, int l, int r, long long v) { if (left == l && right == r) { val[n] += v, tag[n] += v; return; } push(n); int mid(left + right >> 1); if (r <= mid) modify(ls, left, mid, l, r, v); else if (l > mid) modify(rs, mid + 1, right, l, r, v); else modify(ls, left, mid, l, mid, v), modify(rs, mid + 1, right, mid + 1, r, v); update(n); } } std::deque<int> min, max; int n, m, front[maxn]; long long k, s, v[maxn], dp[maxn]; void solve() { int f(0); for (int i(1); i <= n; ++i) { my::getmax(f, front[i]); while (!min.empty() && min.front() <= f) min.pop_front(); int fp(i - 1);//在fp之后的最小值不是v[min.back()],因此只加区间[fr + 1, fp] while (!min.empty() && v[min.back()] >= v[i]) { //v[i]取代v[pos]成为之后的最小值,v[pos]对之后无“贡献” int pos(min.back()); min.pop_back(); int fr(min.empty() ? f : min.back()); seg::modify(1, 1, n, fr + 1, fp, s * (v[pos] - v[i])); //v[i]取代v[pos]成为之后的最小值,故增加s * (v[pos] - v[i]) fp = fr; } min.push_back(i); while (!max.empty() && max.front() <= f) max.pop_front(); fp = i - 1; while (!max.empty() && v[max.back()] <= v[i]) { int pos(max.back()); max.pop_back(); int fr(max.empty() ? f : max.back()); seg::modify(1, 1, n, fr + 1, fp, s * (v[i] - v[pos])); fp = fr; } max.push_back(i); dp[i] = dp[i - 1] + k; if (f + 1 < i) my::getmin(dp[i], seg::quary(1, 1, n, f + 1, i) + k); //从[f + 1, i]中找到一个最合适的位置p使得p与i划分为一段且总代价最小 if (i != n) seg::modify(1, 1, n, i + 1, i + 1, dp[i]); //读者可以体会一下为什么要在i + 1处加上dp[i]以及为什么这么做是对的 } printf("%lld\n", dp[n]); } int main() { scanf("%d%d%lld%lld", &n, &m, &k, &s); for (int i(1); i <= n; ++i) scanf("%d", v + i); for (int i(0); i != m; ++i) { int a, b; scanf("%d%d", &a, &b); if (a > b) std::swap(a, b); my::getmax(front[b], a); } solve(); return 0; }
- 1
信息
- ID
- 2426
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者