UER #7

Yaosicheng124發表於2024-10-09

B. 天路

題目描述

在一根數軸上,有 \(N\) 個點 \(A_1,A_2,\dots,A_N\),你要對於 \(\forall 2\le k\le N\),求出 \(\min\limits_{1\le l\le N-k+1} \{\max \limits_{l\le i< j\le l+k-1}\{|A_i-A_j|\}\}\)

如果對於每個 \(k\),你輸出的答案 \(c_k\) 與標準答案 \(\widehat{c_k}\) 的相對誤差不超過 \(\% 5\),即 \(|c_k-\widehat{c_k}|\le \widehat{c_k}\cdot \% 5\),那麼你的答案將會被接受。

思路

注意到這裡允許你的答案有 \(\% 5\) 的誤差,所以考慮列舉答案,並求出對應的 \(k\)。由於這裡的答案不超過 \(10^6\),並且每次列舉答案 \(x\) 可以直接令 \(x\leftarrow \max(x+1,1.05\cdot x)\),所以只需列舉 \(\log_{1.05} 10^6 \approx 300\) 次。

那麼怎麼求出 \(k\) 呢?我們用雙指標求出能夠覆蓋的極大區間,使用單調佇列維護區間最大/小值即可。

空間複雜度 \(O(N)\),時間複雜度 \(O(N\log_{1.05} V)\),其中 \(V=10^6\)

程式碼

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 100001, MAXV = 1000001;

int n, a[MAXN], que[2][MAXN], head[2], tail[2], ans[MAXN];

int Solve(int x) {
  head[0] = head[1] = 1;
  tail[0] = tail[1] = 0;
  int ret = 0;
  for(int i = 1, j = 0; i <= n; ++i) {
    for(; j <= n && (head[0] > tail[0] || head[1] > tail[1] || a[que[0][head[0]]] - a[que[1][head[1]]] <= x); ) {
      if(++j <= n) {
        for(; head[0] <= tail[0] && a[que[0][tail[0]]] <= a[j]; tail[0]--) {
        }
        que[0][++tail[0]] = j;
        for(; head[1] <= tail[1] && a[que[1][tail[1]]] >= a[j]; tail[1]--) {
        }
        que[1][++tail[1]] = j;
      }
    }
    ret = max(ret, j - i);
    for(; head[0] <= tail[0] && que[0][head[0]] <= i; head[0]++) {
    }
    for(; head[1] <= tail[1] && que[1][head[1]] <= i; head[1]++) {
    }
  }
  return ret;
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n;
  for(int i = 1; i <= n; ++i) {
    cin >> a[i];
    ans[i] = int(1e9) + 1;
  }
  for(int i = 0; i < 2000000; i = max(i + 1, (int)(1.05l * i))) {
    int x = Solve(i);
    ans[x] = min(ans[x], i);
  }
  for(int i = n - 1; i >= 2; --i) {
    ans[i] = min(ans[i], ans[i + 1]);
  }
  for(int i = 2; i <= n; ++i) {
    cout << ans[i] << "\n";
  }
  return 0;
}

C. 套路

題目描述

有一個數列 \(A_1,A_2,\dots,A_N(1\le A_i\le M)\)。令 \(s(l,r)=\min\limits_{l\le i<j\le r}\{|A_i-A_j|\}\),你要求出 \(\max\limits_{1\le l+k-1\le r\le N}\{(r-l)\cdot s(l,r)\}\)

思路

由於 \(s(l,r)\le \frac{M-1}{r-l}\),所以可以想到根號分治。

對於 \(s(l,r)\le \sqrt M\),我們從小到大列舉 \(s(l,r)\)。我們令 \(lst_{i}\) 為最大的 \(j\) 使得 \(|A_i-A_j|<s(l,r)\)。一開始 \(s(l,r)=1\),那麼 \(lst_i\) 就是上次 \(A_i\) 出現的位置,可以輕鬆求出。後來每次 \(s(l,r)\) 加一,只需將 \(lst_i\)\(A_i-s(l,r)+1,A_i+s(l,r)-1\) 出現的位置去 \(\max\) 即可。接著雙指標,用單調佇列維護區間 \(lst_i\) 最大值即可。

對於 \(s(l,r)>\sqrt M\),那麼 \(r-l+1\le \sqrt M\),所以我們可以使用區間 dp,令 \(dp_{i,l}\) 表示區間長度為 \(i\),左端點為 \(l\) 時的 \(s(i,l)\)。很明顯有轉移 \(dp_{i,l}=\min(dp_{i-1,l},dp_{i-1,l+1},|A_l-A_{l+i-1}|)\)。這裡可以用降維最佳化。

空間複雜度 \(O(N+M)\),時間複雜度 \(O((N+M)\sqrt M)\)

程式碼

#include<bits/stdc++.h>
using namespace std;
using ll = long long;

const int MAXN = 200001, B = 447;

int n, m, k, a[MAXN], pos[MAXN], lst[MAXN], que[MAXN], head, tail, dp[MAXN];
ll ans;

int Solve(int x) {
  fill(pos + 1, pos + m + 1, 0);
  int ret = 0;
  for(int i = 1; i <= n; ++i) {
    lst[i] = max({lst[i], (a[i] - x + 1 >= 1 ? pos[a[i] - x + 1] : 0), (a[i] + x - 1 <= m ? pos[a[i] + x - 1] : 0)});
    pos[a[i]] = i;
  }
  head = 1, tail = 0;
  for(int i = 1, j = 0; i <= n; ++i) {
    for(; j <= n && (head > tail || lst[que[head]] < i); ) {
      if(++j <= n) {
        for(; head <= tail && lst[que[tail]] <= lst[j]; --tail) {
        }
        que[++tail] = j;
      }
    }
    ret = max(ret, j - i - 1);
  }
  return ret;
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> m >> k;
  for(int i = 1; i <= n; ++i) {
    cin >> a[i];
    dp[i] = MAXN;
  }
  ll A = 0, b = 0;
  for(int i = 1; i <= min(m, (MAXN - 1 + B - 1) / B); ++i) {
    ll x = Solve(i);
    if(x + 1 >= k) {
      ans = max(ans, 1ll * x * i);
      A = max(A, 1ll * x * i);
    }
  }
  for(int i = 2; i <= min(n, B); ++i) {
    for(int l = 1, r = i; r <= n; ++l, ++r) {
      dp[l] = min({dp[l], abs(a[l] - a[r]), dp[l + 1]});
      if(i >= k) {
        ans = max(ans, 1ll * (i - 1) * dp[l]);
        b = max(b, 1ll * dp[l] * (i - 1));
      }
    }
  }
  cout << ans;
  return 0;
}

相關文章