SP9070
模拟赛场切了,发篇题解纪念一下。
首先对题目中的不等式移项,得到 \(p \ge h_j + \sqrt{|i-j|} - h_i\)。对于右边部分的式子,\(h_i\) 是确定的,因此只需求出 \(h_j + \sqrt{|i-j|}\) 的最大值即可(也就是要求的最小高度)。
观察式子,发现 \(\sqrt{|i-j|}\) 随着 \(i\) 的变化而不停地变化,这意味着每次都要 \(O(n)\) 求出这部分的值,稳稳地 TLE 了。然而事实上真的要每次重新求全部吗?
我们首先看回这个式子本身。据题意可知,这个值需要向上取整。因此 \(\sqrt 1,\sqrt 2,\sqrt 3,\sqrt 4,\sqrt 5\) 分别对应着 \(1,2,2,2,3\)。这样似乎比浮点数方便很多了。但其中有什么规律呢?我们以 \(n=6\) 为例说明。记当前位置为 \(nw\)。
首先令 \(nw=1\),此时各点的对应式子的值为:
接下来 \(nw=2\),变化为:
观察到只有第 \(1,2,3,6\) 个位置的值发生了变化。再看看 \(nw=3\):
变化的位置依旧是 \(4\) 个。除去当前位置即为 \(3\) 个,大约为 \(\sqrt 6\) 个。大胆猜想,对于长度为 \(n\) 的序列,如果每次只对变化的位置进行修改,则只需要 \(O(\sqrt n)\) 的复杂度。简单证明一下,如果 \(|i-j|\) 是完全平方数,那么当两者距离增大 \(1\) 时,根据上面对题目分析,其开方向上取整后一定会比原来大 \(1\)。如果 \(|i-j|\) 不是完全平方数,那么距离增大 \(1\) 后最多只能达到对应的完全平方数,因此不会产生影响。
有了上面的分析,接下来实现就不难了。先从前往后扫一遍,每次不断按照上面思路进行单点更新,顺便求出 \(h_j + \sqrt{|i-j|}\) 的前缀最大值。同理再从后往前扫一遍,求出后缀最大值。最后对于每个 \(i\),用对应的前缀和后缀取最大值即可。
总时间复杂度 \(O(n \sqrt n )\),由于常数小,因此能够较为轻松地通过本题。
#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;
ll n,a[1000001],mxl,mxr,l[1000001],r[1000001],x[1000001],y[1000001];
int main()
{
cin >> n;
for( int i = 1 ; i <= n ; i ++ )
scanf( "%lld" , &a[i] ),l[i] = r[i] = a[i];
for( int i = n ; i >= 1 ; i -- )
{
for( int j = i + 1,k = 1 ; j <= n ; j += k , k += 2 )
r[j] ++,mxr = max( mxr , r[j] );
y[i] = max( 0ll , mxr - a[i] );
}
for( int i = 1 ; i <= n ; i ++ )
{
for( int j = i - 1,k = 1 ; j >= 1 ; j -= k , k += 2 )
l[j] ++,mxl = max( mxl , l[j] );
x[i] = max( 0ll , mxl - a[i] );
}
for( int i = 1 ; i <= n ; i ++ )
printf( "%lld\n" , max( x[i] , y[i] ) );
return 0;
}

浙公网安备 33010602011771号