LOJ 4789 「RMI 2020」Peru

考虑到区间可能会有交,这个时候肯定会贪心的让这部分的权值为偏大的一部分。
于是考虑把条件转化为由若干个长度 \(\le k\) 的不交区间覆盖。

那么如果对应的区间是 \([l, r]\),那么贪心的,这个区间选出来的权值就会是 \(\max\limits_{i = l}^r s_i\)

那么就可以设出 dp。
定义 \(f_i\) 为最后一段右端点是 \(i\) 的最小花费。
转移直接枚举最后一段的左端点即可,就有 \(f_i = \min\limits_{j = i - k + 1}^i\{f_{j - 1} + \max\limits_{p = j}^i s_p\}\)

那么对于这个 \(\max\limits_{p = j}^i s_p\) 的权值,就肯定需要一个单调栈了。
对于单调栈,假设内部相邻的两个元素是 \(x < y\),那么对于 \(y\) 转移给的值一定就是 \(f_x + s_y\),这是因为 \(f\) 不减。
同时对于单调栈中的栈底 \(x\)(最小的),其贡献一定是 \(f_{i - k} + s_x\),因为区间长度不能超过 \(k\)

但是同时这个单调栈也会因为区间长度不超过 \(k\) 的限制从栈底弹出下标。
相当于这维护的其实是一个双端队列。

考虑要进行什么操作:

  • 末尾加入元素。
  • 开头 / 末尾删除元素。
  • 查询队列内贡献最小值。

这时有一个方法是用双栈来模拟这个过程。
具体来说,维护两个栈 \(L, R\),其中 \(L\) 的栈顶到 \(L\) 的栈底再到 \(R\) 的栈底再到 \(R\) 的栈顶拼成了完整的双端队列。

那么对于末尾加入元素,就可以直接在 \(R\) 加入栈中,对于前驱分讨一下就可以知道。

假设 \(L, R\) 不会在中途出现空了还需要删的情况,考虑队列最小值怎么处理。
首先对于删除还是直接从对应的 \(L, R\) 中弹出栈顶。
那么能发现这个最小值肯定是 \(L, R\) 的两个栈各自的最小值中的最小值。
那么对于 \(L, R\) 两个栈各自的最小值,发现肯定是栈底到当前的顶的最小值,于是对于每个栈各维护一个从栈顶开始的“前缀”最小值(其实就是栈顶到该位置的最小值)即可。
那么这个值就可以在加入时直接更新,删除时因为新的栈顶的信息已经处理过了,就可以直接用了。

那么这部分的时间复杂度都是 \(\mathcal{O}(n)\) 的。

接下来就考虑怎么处理“\(L, R\) 空时还需要删”的情况。
那么比较直接的想法就是保证 \(|L|, |R| > 0\),当然这个要基于 \(|L| + |R| \ge 2\)

于是考虑在 \(|L| + |R| \ge 2\)\(|L| = 0\) 或者 \(|R| = 0\) 时直接暴力重构使得 \(|L|, |R| > 0\)
于是考虑一些重构使得时间复杂度比较优秀,一个比较的直观的想法就是让 \(|L|, |R|\) 都能保持非空的状态尽量久。
那就可以令 \(|L'| = \lfloor\frac{|L| + |R|}{2}\rfloor, |R'| = \lceil\frac{|L| + |R|}{2}\rceil\)
重构肯定是能做到关于 \(|L| + |R|\) 线性的,直接暴力重新推贡献更新前缀最小值即可。

接下来考虑复杂度分析,考虑令 \(m = \max\{|L|, |R|\}\)
考虑每次如果是加入操作,那么 \(m'\le m + 1\),如果是删除操作,那么 \(m'\le m\)
如果是重构,那么花费 \(m\) 的代价,有 \(m' = \lceil\frac{m}{2}\rceil\)

考虑重构时 \(m - m' = \lfloor\frac{m}{2}\rfloor\) 个元素,此时定义这些元素被删掉了。
此时就可以把 \(m\) 摊到这 \(\lfloor\frac{m}{2}\rfloor\) 个元素上,即每个被删除的元素带有 \(2\) 的代价,且每次重构操作可能会多带有 \(1\) 的代价。

因为一共加入 \(n\) 次,\(m\leftarrow m + 1\) 的次数也不会超过 \(n\) 次,被删除的元素个数就不会超过 \(n\) 个。
且重构的次数也是 \(\mathcal{O}(n)\) 的。
所以最后重构的复杂度也是 \(\mathcal{O}(n)\) 的。

于是最后的复杂度就是 \(\mathcal{O}(n)\)

代码写的有点丑。

#include<bits/stdc++.h>
using ll = long long;
constexpr ll inf = 1e18;
constexpr ll mod = 1e9 + 7;
constexpr int maxn = 2.5e6 + 10;
ll f[maxn], s[maxn];
struct que {
   // top2 >= top1
   int stk1[maxn], top1; ll val1[maxn], mn1[maxn];
   int stk2[maxn], top2; ll val2[maxn], mn2[maxn];
   inline que() {
      top1 = top2 = 0;
      val1[0] = val2[0] = mn1[0] = mn2[0] = inf;
   }
   inline void update() {
      if (top1 && top2) return ;
      if (top1 + top2 == 0) return ;
      if (top1 + top2 == 1) {
         int x = top1 ? stk1[top1] : stk2[top2];
         top1 = 0, top2 = 1, stk2[1] = x;
         val2[1] = mn2[1] = inf;
         return ;
      }
      if (! top1) {
         top1 = top2 / 2, top2 -= top1;
         for (int i = 1; i <= top1; i++) {
            stk1[top1 - i + 1] = stk2[i];
         }
         for (int i = 1; i <= top2; i++) {
            stk2[i] = stk2[i + top1];
         }
      } else {
         top2 = (top1 + 1) / 2, top1 -= top2;
         for (int i = 1; i <= top2; i++) {
            stk2[top2 - i + 1] = stk1[i];
         }
         for (int i = 1; i <= top1; i++) {
            stk1[i] = stk1[i + top2];
         }
      }
      val1[top1] = inf;
      for (int i = 1, x, y; i < top1; i++) {
         x = stk1[i], y = stk1[i + 1];
         val1[i] = s[x] + f[y];
      }
      val2[1] = s[stk2[1]] + f[stk1[1]];
      for (int i = 2, x, y; i <= top2; i++) {
         x = stk2[i], y = stk2[i - 1];
         val2[i] = s[x] + f[y];
      }
      for (int i = 1; i <= top1; i++) {
         mn1[i] = std::min(val1[i], mn1[i - 1]);
      }
      for (int i = 1; i <= top2; i++) {
         mn2[i] = std::min(val2[i], mn2[i - 1]);
      }
   }
   inline void push_back(int x) {
      top2++;
      stk2[top2] = x, val2[top2] = top2 > 1 ? (s[x] + f[stk2[top2 - 1]]) : (ll)inf;
      mn2[top2] = std::min(val2[top2], mn2[top2 - 1]);
      update();
   }
   inline void pop_back() {
      top2--;
      update();
   }
   inline void pop_front() {
      if (top1) {
         top1--;
         if (top1) {
            val1[top1] = inf, mn1[top1] = mn1[top1 - 1];
         }
      } else {
         top2--;
      }
      update();
   }
   inline bool empty() {
      return top2 == 0;
   }
   inline int front() {
      return top1 ? stk1[top1] : stk2[1];
   }
   inline int back() {
      return stk2[top2];
   }
   inline ll query() {
      return std::min(mn1[top1], mn2[top2]);
   }
} Q;
int solve(int n, int k, int* s_){
   ll ans = 0;
   for (int i = 0; i < n; i++) {
      s[i] = s_[i];
      if (! Q.empty() && i - Q.front() >= k) Q.pop_front();
      while (! Q.empty() && s[i] > s[Q.back()]) Q.pop_back();
      Q.push_back(i);
      f[i] = std::min(Q.query(), s[Q.front()] + (i - k >= 0 ? f[i - k] : 0));
      ans = (23ll * ans + f[i]) % mod;
   }
   for (int i = 1; i < n; i++) {
      assert(f[i] >= f[i - 1]);
   }
   return (int)ans;
}

posted @ 2024-12-24 15:22  rizynvu  阅读(43)  评论(0)    收藏  举报