1. 程式人生 > >bzoj2216 [Poi2011]Lightning Conductor(決策單調性DP)

bzoj2216 [Poi2011]Lightning Conductor(決策單調性DP)

bzoj2216 [Poi2011]Lightning Conductor

題意:
已知一個長度為n的序列a1,a2,…,an。
對於每個1<=i<=n,找到最小的非負整數p滿足 對於任意的j, aj < = ai + p - sqrt(abs(i-j))

資料範圍
1<=n<=500000,0<=ai<=1000000000

題解:
好題。
對於abs的處理就直接正反各跑一遍。

pi>=max(aj+|ij|)ai 決策與ai無關。
因為x這東西是漲得越來越慢的,決策點是單調的。

(於是傻逼的我想都沒想就直接佇列首尾彈一彈以為能O(n)然後並不行。
因為在i移動時,佇列中的點的大小關係可能發生變化,不知道什麼時候該彈。
反過來思考,每個點對其後方的點的影響,以它作為答案的是連續的一段區間。
需要求出每個點準確的能夠控制的區間。)

於是在加入點時計算出其能夠控制的區間,這個過程用二分,以便彈出。
注意,過程中必須要保證L>=i,
否則由於不能保證j< i,sqrt()誰漲得快不一定,下面彈棧不能那麼彈 。

UPD:整體二分好啊,好寫好調,哪像單調佇列

程式碼:

#include<cstdio>
#include<iostream>
#include<algorithm> #include<cstring> #include<cmath> using namespace std; const int N=500005; const int inf=0x3f3f3f3f; struct node { int L,R,id; node(){} node(int L,int R,int id):L(L),R(R),id(id){} }Q[N]; int a[N],f[N],n; double cal(int j,int i) {return (double)a[j]-a[i]+sqrt
(abs(i-j));} int find(int x,int y,int l,int r) { int lf=l; int rg=r; while(lf+1<rg) { int mid=(lf+rg)>>1; if(cal(x,mid)<cal(y,mid)) lf=mid; else rg=mid; } if(cal(x,rg)<cal(y,rg)) return rg; else return lf; } void getans() { int h=1,t=0; for(int i=1;i<=n;i++) { Q[h].L++; while(h<=t&&(Q[h].R<i||Q[h].L>Q[h].R)) h++; if(h>t) Q[++t]=node(i,n,i); else if(cal(i,n)>cal(Q[t].id,n)) { while(h<=t&&cal(i,Q[t].L)>=cal(Q[t].id,Q[t].L)) t--; if(h<=t) { int pos=find(i,Q[t].id,Q[t].L,Q[t].R); Q[t].R=pos; Q[++t]=node(pos+1,n,i); } else Q[++t]=node(i,n,i); } f[i]=max(f[i],(int)ceil(cal(Q[h].id,i))); } } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); memset(f,0,sizeof(f)); getans(); for(int i=1;i+i<=n;i++) {swap(a[i],a[n-i+1]); swap(f[i],f[n-i+1]);} getans(); for(int i=1;i<=n;i++) printf("%d\n",f[n-i+1]); return 0; }