UOJ #919. 【UR #28】環環相扣 題解

下蛋爷發表於2024-11-26

Description

給定一個長度為 \(n\) 的整數序列 \(a_1\sim a_n\),其中的元素兩兩互不相等。

\(q\) 個詢問,每個詢問給定一個區間 \([l,r]\),你要選擇三個下標 \(i,j,k\in[l,r]\) 滿足 \(i\neq j,j\neq k,k\neq i\),最大化 \((a_i\bmod a_j)+(a_j\bmod a_k)+(a_k\bmod a_i)\) 的值。

你只需要輸出這個最大值。

\(3\leq n\leq2\times10^6\)\(1\leq q\leq8\times10^5\)\(\text{op}\in\{0,1\}\)\(1\leq a_i\leq10^{18}\)\(a_1\sim a_n\) 互不相等,\(1\leq l\leq r\leq n\)\(r-l+1\geq3\)

Solution

不妨設 \(a_x>a_y>a_z\),那麼對於 \((x,y,z)\) 只有兩種貢獻:\(a_x\bmod a_y+a_y\bmod a_z+a_z\)\(a_x\bmod a_z+a_z+a_y\)

對於一組詢問 \([l,r]\),有個結論是 \([l,r]\) 內的區間最大值和次大值都必須選。

  • 證明

    先把區間的數拿出來並排序,使得 \(a_1<a_2<\ldots<a_m\),則選擇 \((m-2,m-1,m)\) 可以得到一個答案下界為 \(a_{m-1}+a_{m-2}\)

    • 如果最終答案為 \(a_x\bmod a_y+a_y\bmod a_z+a_z\),由於 \(a_x\bmod a_y+a_y\bmod a_z+a_z\leq\min\{a_x,2\cdot a_y-1\}\),則 \(x\leq m-1\)\(y\leq m-2\) 一定沒上面那個優,所以 \(x=m\)\(y=m-1\)

    • 如果最終答案為 \(a_x\bmod a_z+a_z+a_y\),由於 \(a_x\bmod a_z+a_z+a_y\leq a_x+a_y\),當 \(x\neq m\) 一定達不到最優解,又因為 \(a_y\) 只出現了一次,所以 \(y\) 一定儘量取到 \(m-1\)

於是 \(x\)\(y\) 就固定了,設 \(F(x,l,r)\) 表示將 \([l,r]\) 中的 \(x\) 和把剩下的最大值去掉後的所有 \(k\)\(a_x\bmod a_k+a_k\) 的最大值。

那麼答案就是 \(\max\{F(x,l,r)+a_y,F(y,l,r)+a_x\bmod a_y\}\),由於 \(x,y\) 已經確定,所以我們只需要求出 \(F(x,l,r)\) 的值即可。


考慮將 \(F(x,l,r)\) 拆成 \(F(x,l,x-1)\)\(F(x,x+1,r)\)。對於 \(F(x,l,x-1)\),讓 \(l\)\(x\) 列舉到 \(1\) 可以得到一個 \(O(n^2)\) 的做法。

又有個結論是如果掃到了某個 \(l\),如果存在至少兩個 \(a_i>\frac{a_x}{2}\) 就可以停止掃描。

  • 證明

    不妨設這兩個數是 \(a_i,a_j\)\(a_i<a_j\)

    • 如果 \(a_i>a_x\),與 \(x\) 為區間最大值/次大值矛盾,這個區間一定不會被詢問到。

    • 如果 \(\frac{a_x}{2}<a_i<a_x\),則 \(a_i\bmod a_x+a_x=a_i\),已經到了最大值,前面的一定不會更優。

基於這個做法暴力列舉 \(l\) 可以做到 \(O(n\log V+q\log n)\),過不了。


還有個結論是掃描到 \(a_i\) 時,如果已經存在兩個數 \(\geq 2\cdot a_i\),則 \(a_i\) 就可以刪掉。

  • 證明

    如果 \(a_j\geq 2\cdot a_i\),則 \(a_x\bmod a_j+a_j\geq a_j\geq 2\cdot a_i>a_x\bmod a_i+a_i\),所以 \(a_i\) 一定不會對答案造成貢獻。

所以可以在從小到大列舉 \(x\) 的過程中,維護一個棧表示目前還沒刪掉的數和這些數的刪除標記。然後在掃描 \(l\) 的過程中,維護另一個標記表示 \(>\frac{a_x}{2}\) 的個數。如果當前 \(a_i\leq \frac{a_x}{2}\) 就將 \(i\) 的刪除標記加 \(1\)。否則將另一個標記加一,如果另一個標記到了 \(2\) 就停止掃描,並把棧裡面刪除標記為 \(2\) 的數刪掉,並將 \(x\) 加到棧裡。

容易證明上面那個做法的預處理複雜度為 \(O(n)\)

時間複雜度:\(O(n+q\log n)\)

Code

#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 2e6 + 5;

int n, q, tp;
int64_t a[kMaxN];
std::vector<std::pair<int, int64_t>> vecl[kMaxN], vecr[kMaxN];

int get(int x, int y) { return a[x] > a[y] ? x : y; }

struct SGT {
  int N, mx[kMaxN * 4];

  void pushup(int x) {
    mx[x] = get(mx[x << 1], mx[x << 1 | 1]);
  }

  void build(int n) {
    for (N = 1; N <= n + 1; N <<= 1) {}
    for (int i = N; i <= N + n; ++i) mx[i] = i - N;
    for (int i = N - 1; i; --i) pushup(i);
  }

  int query(int l, int r) {
    int ret = 0;
    for (l += N - 1, r += N + 1; l ^ r ^ 1; l >>= 1, r >>= 1) {
      if (~l & 1) ret = get(ret, mx[l ^ 1]);
      if (r & 1) ret = get(ret, mx[r ^ 1]);
    }
    return ret;
  }
} sgt;

void getl() {
  static int stk[kMaxN] = {0}, cnt[kMaxN] = {0}, tmp[kMaxN];
  int top = 0;
  for (int i = 1; i <= n; ++i) {
    int now = 0, mx = 0, cur = top + 1;
    int64_t res = LLONG_MIN;
    for (int j = top; j; --j) {
      if (!mx) {
        mx = stk[j];
      } else if (a[stk[j]] < a[mx]) {
        res = std::max(res, a[i] % a[stk[j]] + a[stk[j]]);
      } else {
        res = std::max(res, a[i] % a[mx] + a[mx]);
        mx = stk[j];
      }
      vecl[i].emplace_back(stk[j], res);
      if (a[stk[j]] > a[i] / 2) {
        if (++now == 2) break;
      } else {
        ++cnt[stk[j]];
      }
      cur = j;
    }
    int m = 0;
    for (int j = cur; j <= top; ++j)
      if (cnt[stk[j]] < 2)
        tmp[++m] = stk[j];
    top = cur - 1;
    for (int j = 1; j <= m; ++j) stk[++top] = tmp[j];
    stk[++top] = i;
    std::reverse(vecl[i].begin(), vecl[i].end());
  }
}

void getr() {
  static int stk[kMaxN] = {0}, cnt[kMaxN] = {0}, tmp[kMaxN];
  int top = 0;
  for (int i = n; i; --i) {
    int now = 0, mx = 0, cur = top + 1;
    int64_t res = LLONG_MIN;
    for (int j = top; j; --j) {
      if (!mx) {
        mx = stk[j];
      } else if (a[stk[j]] < a[mx]) {
        res = std::max(res, a[i] % a[stk[j]] + a[stk[j]]);
      } else {
        res = std::max(res, a[i] % a[mx] + a[mx]);
        mx = stk[j];
      }
      vecr[i].emplace_back(stk[j], res);
      if (a[stk[j]] > a[i] / 2) {
        if (++now == 2) break;
      } else {
        ++cnt[stk[j]];
      }
      cur = j;
    }
    int m = 0;
    for (int j = cur; j <= top; ++j)
      if (cnt[stk[j]] < 2)
        tmp[++m] = stk[j];
    top = cur - 1;
    for (int j = 1; j <= m; ++j) stk[++top] = tmp[j];
    stk[++top] = i;
    std::reverse(vecr[i].begin(), vecr[i].end());
  }
}

void prework() {
  sgt.build(n);
  getl(), getr();
}

int64_t F(int x, int l, int r, int mx) {
  int64_t ret = LLONG_MIN;
  int y = sgt.query(l, x - 1), z = sgt.query(x + 1, r);
  if (y && y != mx) ret = std::max(ret, a[y] + a[x] % a[y]);
  if (z && z != mx) ret = std::max(ret, a[z] + a[x] % a[z]);
  auto it1 = std::lower_bound(vecl[x].begin(), vecl[x].end(), std::pair<int, int64_t>{l, LLONG_MIN});
  auto it2 = std::lower_bound(vecr[x].begin(), vecr[x].end(), std::pair<int, int64_t>{r, LLONG_MAX}, std::greater<>());
  if (it1 != vecl[x].end()) ret = std::max(ret, it1->second);
  if (it2 != vecr[x].end()) ret = std::max(ret, it2->second);
  return ret;
}

int64_t query(int l, int r) {
  int x = sgt.query(l, r), y = get(sgt.query(l, x - 1), sgt.query(x + 1, r));
  return std::max(F(x, l, r, y) + a[y], F(y, l, r, x) + a[x] % a[y]);
}

void dickdreamer() {
  std::cin >> n >> q >> tp;
  for (int i = 1; i <= n; ++i) std::cin >> a[i];
  prework();
  int64_t lastans = 0;
  for (int i = 1; i <= q; ++i) {
    int l, r;
    std::cin >> l >> r;
    l = (l + lastans * tp - 1) % n + 1;
    r = (r + lastans * tp - 1) % n + 1;
    std::cout << (lastans = query(l, r)) << '\n';
  }
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

相關文章