luogu P4655 [CEOI2017]Building Bridges
题面传送门
蛮好的一道题目。
题面其实错了,还有只能从左往右建。
考虑暴力\(dp\):设\(q_i=\sum\limits_{j=1}^{i}{w_i}\),则\(dp_i=\min\limits_{j=1}^{i-1}{dp_j+q_{i-1}-q_j+(h_i-h_j)^2}\)
显然可以斜优,设\(k<j\)且\(k\)劣于\(j\),则\(dp_j+q_{i-1}-q_j+(h_i-h_j)^2<dp_k+q_{i-1}-q_k+(h_i-h_k)^2\)
展开,可得\(dp_k-q_k+h_k^2-2h_ih_k>dp_j-q_j+h_j^2-2h_ih_j\)
设\(Y(a)=dp_a-q_a+h_a^2\),移项就有\(2h_i>\frac{Y(j)-Y(k)}{h_j-h_k}\)
大问题来了:\(h_i\)非递增。如果\(Y(a)\)递增,我们可以在栈中二分查到最小值。但是\(Y(a)\)同样不递增。
考虑\(cdq\)分治来处理出可以转移的区间。先按位置排序。
先递归处理左区间,这样才能构建左区间的斜优。然后处理中间,最后处理右区间。
同时为了保证单调性,要先对左右两部分各自按\(h_i\)排序,然后构建出左边的单调栈,右边查询即可。
注意递归右区间时区间要排回去。
时间复杂度\(o(nlog^2n)\),如果预先归并排序处理每一层的有序序列则可达到\(O(nlogn)\),空间上由\(O(n)\)变成\(O(nlogn)\)因为代码太难打所以没写
代码实现:
#include<cstdio>
#include<algorithm>
#define min(a,b) ((a)<(b)?(a):(b))
#define Y(a) (s[a].dp-s[a].w+s[a].h*s[a].h)
#define X(a) (s[a].h)
using namespace std;
int n,m,k,x,y,z,q[100039],head,tail,w[100039];
struct yyy{long long h,w,id,dp,q;}s[100039];
inline bool cmp1(yyy x,yyy y){return x.h<y.h;}
inline bool cmp2(yyy x,yyy y){return x.id<y.id;}
inline double slope(int x,int y){return (Y(x)-Y(y))*1.0/(X(x)==X(y)?-1e-9:(X(x)-X(y)));}
inline void make(int x,int y){
int i;head=1;tail=0;
for(i=x;i<=y;i++){
while(head<tail&&slope(q[tail-1],q[tail])>slope(q[tail],i)) tail--;
q[++tail]=i;
//printf("%d %d %d %d %d\n",x,y,head,tail,i);
}
}
inline void slove(int x,int y){
if(x==y) return;
int m=x+y>>1,l=x,i,j;
slove(x,m);
sort(s+x,s+m+1,cmp1);sort(s+m+1,s+y+1,cmp1);
make(x,m);
for(i=m+1;i<=y;i++){
while(head<tail&&slope(q[head],q[head+1])<2*s[i].h) head++;//printf("%d %d %d %d %d %lf\n",x,y,head,tail,s[i].h,slope(q[head],q[head+1]));
j=q[head];s[i].dp=min(s[i].dp,s[j].dp+s[i].w-s[i].q-s[j].w+(s[i].h-s[j].h)*(s[i].h-s[j].h));//printf("%lld %lld %d %d %d\n",s[i].id,s[i].dp,x,y,s[j].id);
}
sort(s+m+1,s+y+1,cmp2);
slove(m+1,y);
}
int main(){
freopen("1.in","r",stdin);
register int i;
scanf("%d",&n);
for(i=1;i<=n;i++) scanf("%lld",&s[i].h),s[i].id=i,s[i].dp=1e18;
s[1].dp=0;
for(i=1;i<=n;i++) scanf("%lld",&s[i].w),s[i].q=s[i].w,s[i].w+=s[i-1].w;
slove(1,n);
sort(s+1,s+n+1,cmp2);
printf("%lld\n",s[n].dp);
}