瘋狂%%%幾個月前就秒了此題的Tyher巨佬
借着這題總結一下決策單調性優化DP吧。蒟蒻覺得用數形結合的思想能夠輕松地理解它。
首先,題目要我們求所有的\(p_i\),那么把式子變一下
絕對值看着很不爽,我們把它拆開
單獨看前一部分
很明顯是個要用決策單調性優化的式子。把序列翻轉以后,后一部分的算法和前面是一樣的,所以只討論前一部分了。
對於每個\(j\),把\(a_j+\sqrt{i-j}\)看成關於\(i\)的函數\(f_j\)。我們要做的就是在所有\(j\leq i\)的函數中找到最值。比如樣例:
觀察發現,真正有用的函數只有最上面那個!然而實際情況比這個稍復雜些。sqrt的增速是遞減的,因此可能存在一個\(j\)比較小的函數,在某一時刻被\(j\)比較大的函數反超。我們大概需要維護這樣的若干個函數:
我們用隊列實現決策二分棧(不懂的可以參考一下蒟蒻的blog),按\(j\)從小到大依次維護這些函數。顯然,對於其中任意兩個相鄰的函數\(f_t,f_{t+1}\),它們都有一個臨界值\(k_{t,t+1}\)。顯然序列中的\(k_{1,2},k_{2,3}...\)也要嚴格遞增。否則,如果\(k_{t,t+1}\ge k_{t+1,t+2}\),可以想象\(f_{t+1}\)根本沒有用。
先for
一遍\(i\),我們嘗試着把\(f_i\)加入隊列。這時候為了保證\(k\)遞增,設隊尾決策為\(t\),我們判斷,如果\(k_{t-1,t}\ge k_{t,i}\)(此時會有\(f_t(k_{t-1,t})\le f_i(k_{t-1,t})\)),那么\(t\)沒用,出隊。
該出去的都出去后,\(i\)就可以加入隊尾了。這時候可以來求\(p_i\)了。我們檢查一下隊首決策\(h\),如果\(t_{h,h+1}\le i\),說明\(h\)的巔峰時刻已經過去,出隊。最后隊首就是所有函數中的最大值。
貌似並沒有用到什么三元組啊qwq
update:感謝孤獨·粲澤的指正,二分上下界確實該調調
不過還是沒有用到什么三元組啊qwq,蒟蒻之前都把臨界值\(k\)存下了,直接用就可以啦
#include<bits/stdc++.h>
#define RG register
#define R RG int
#define G if(++ip==iend)fread(ip=buf,1,N,stdin)
#define calc(i,j) a[j]+sq[i-j]//計算函數值
using namespace std;
const int N=5e5+9;
char buf[N],*iend=buf+N,*ip=iend-1;
int n,a[N],q[N],k[N];
double p[N],sq[N];
inline int in(){
G;while(*ip<'-')G;
R x=*ip&15;G;
while(*ip>'-'){x*=10;x+=*ip&15;G;}
return x;
}
inline void chkmx(RG double&x,RG double y){
if(x<y)x=y;
}
inline int bound(R x,R y){//二分臨界值k
R l=y,r=k[x]?k[x]:n,m,ret=r+1;//控制二分上下界
while(l<=r){
m=(l+r)>>1;
if(calc(m,x)<=calc(m,y))
ret=m,r=m-1;
else l=m+1;
}
return ret;
}
void work(){
for(R h=1,t=0,i=1;i<=n;++i){
while(h<t&&calc(k[t-1],q[t])<calc(k[t-1],i))--t;//維護k單調
k[t]=bound(q[t],i);q[++t]=i;
while(h<t&&k[h]<=i)++h;//將已經不優的決策出隊
chkmx(p[i],calc(i,q[h]));//因為做兩遍所以取max
}
}
int main(){
n=in();
R i,j;
for(i=1;i<=n;++i)
a[i]=in(),sq[i]=sqrt(i);
work();
for(i=1,j=n;i<j;++i,--j)//序列翻轉
swap(a[i],a[j]),swap(p[i],p[j]);
work();
for(R i=n;i;--i)//翻轉過了所以要倒着輸出
printf("%d\n",max((int)ceil(p[i])-a[i],0));
return 0;
}