P r o b l e m \mathrm{Problem} Problem
给定一个长度为 n n n 的序列 { a n } \{a_n\} { an?},对于每个 i ∈ [ 1 , n ] i\in [1,n] i∈[1,n] ,求出一个最小的非负整数 p p p ,使得 ? j ∈ [ 1 , n ] \forall j\in[1,n] ?j∈[1,n],都有 a j ≤ a i + p ? ∣ i ? j ∣ a_j\le a_i+p-\sqrt{|i-j|} aj?≤ai?+p?∣i?j∣?
1 ≤ n ≤ 5 × 1 0 5 1 \le n \le 5\times 10^{5} 1≤n≤5×105, 0 ≤ a i ≤ 1 0 9 0 \le a_i \le 10^{9} 0≤ai?≤109 。
S o l u t i o n \mathrm{Solution} Solution
如果我们设 f i f_i fi? 表示当前数字 i i i的答案,那么显然有方程: f i = max ? ( f j + ∣ i ? j ∣ ? f i ) f_i=\max(f_j+\sqrt{|i-j|}-f_i) fi?=max(fj?+∣i?j∣??fi?)
那么我们将方程内的绝对值拆开并分类讨论,则有: f i = max ? ( f j + i ? j ? f j ) ( j ≤ i ) f_i=\max(f_j+\sqrt{i-j}-f_j)(j\le i) fi?=max(fj?+i?j??fj?)(j≤i)
f i = max ? ( f j + j ? i ? f j ) ( i ≤ j ) f_i=\max(f_j+\sqrt{j-i}-f_j)(i\le j) fi?=max(fj?+j?i??fj?)(i≤j)
那么我们就将题目转化为了两个问题,我们通过达标发现:
- 第一个状态转移方程的决策点单调不下降。
- 第二个状态转移方程的决策点单调不上升。
观察到 { f i } \{ f_i \} { fi?}的转移线性无关。我们考虑分治法来优化DP。
函数 c a l c ( l , r , L , R ) \mathrm{calc(l,r,L,R)} calc(l,r,L,R)表示我们需要转移 [ l , r ] [l,r] [l,r]内的状态,其中可选的决策是 [ L , R ] [L,R] [L,R]。
- 每一次取出中点,找到中点的决策点 p p p。
- 递归查找 c a l c ( l , m i d ? 1 , L , p ) \mathrm{calc(l,mid-1,L,p)} calc(l,mid?1,L,p)和 c a l c ( m i d + 1 , r , p , R ) \mathrm{calc(mid+1,r,p,R)} calc(mid+1,r,p,R)。
这样,我们就优化掉了一重 O ( n ) O(n) O(n)的复杂度,总复杂度为 O ( n l o g n ) . O(nlogn). O(nlogn).
C o d e \mathrm{Code} Code
#include <bits/stdc++.h> using namespace std;
const int N = 6e5;int n;
double a[N], f1[N], f2[N];int read(void)
{
int s = 0, w = 0; char c = getchar();while (c < '0' || c > '9') w |= c == '-', c = getchar();while (c >= '0' && c <= '9') s = s*10+c-48, c = getchar();return w ? -s : s;
}void Solve1(int l, int r, int ll, int rr)
{
if (l > r) return;int p = 0, mid = l + r >> 1;double res = 0;for (int i=ll;i<=min(mid,rr);++i){
double val = a[i] + sqrt(mid - i) - a[mid];if (val > f1[mid]) f1[mid] = val, p = i;}Solve1(l, mid-1, ll, p);Solve1(mid+1, r, p, rr);return;
}void Solve2(int l, int r, int ll, int rr)
{
if (l > r) return;int p = 0, mid = l + r >> 1;double res = 0;for (int i=max(ll,mid);i<=rr;++i){
double val = a[i] + sqrt(i - mid) - a[mid];if (val > f2[mid]) f2[mid] = val, p = i;}Solve2(l, mid-1, ll, p);Solve2(mid+1, r, p, rr);return;
}int main(void)
{
n = read();for (int i=1;i<=n;++i) a[i] = 1.0 * read();Solve1(1, n, 1, n);Solve2(1, n, 1, n);for (int i=1;i<=n;++i) printf("%.0lf\n", ceil(max(f1[i], f2[i])));return 0;
}