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;
}