【HNOI2008】玩具装箱 题解
HNOI2008
Tham题目的难度跨度真大。
核心思路
首先有一个很显然的dp,设dpi为前i个的最小花费
dpi=j=0mini−1{dpj+(i−j−1+sumi−sumj−L)2}
斜率优化的套路就是把含i、j的分离开来,为了更加方便:
ai=sumi+i , bi=sumi+i+L+1
方程转化为:
dpidpi=j=0mini−1[(ai−bj)2+dpj]=(ai−bj)2+dpj
2×aibj+dpi−ai2=dpj+bj2
再将这个式子看做一个一次函数,ai是已知的
k=2ai,x=bj,y=dpj+bj2
y=kx+dpi−ai2
dpi=y−kx+ai2
任务是要找到dpi的最小值
数形结合可以理解为,上述直线过点(bj,dpj+bj2),直线在y轴截距增加ai2。
最优解即在这些点的下凸包上。
用单调队列维护一下就可以求出下凸包。边求边统计答案。
## 完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include<iostream> #include<cstdio> #include<cmath> #include<cstring> using namespace std;
#define int ll
#define REP(i,e,s) for(register int i=e; i<=s; i++) #define DREP(i,e,s) for(register int i=e; i>=s; i--) #define ll long long #define DE(...) fprintf(stderr,__VA_ARGS__) #define DEBUG(a) DE("DEBUG: %d\n",a) #define file(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout) int read() { int x=0,f=1,ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; }
const int MAXN=50000+10;
int c[MAXN],f[MAXN],q[MAXN],a[MAXN],b[MAXN];
double calc(int i,int j) { int yi=f[i]+b[i]*b[i],yj=f[j]+b[j]*b[j]; return 1.0*(yi-yj)/(b[i]-b[j]); }
signed main() { int n=read(),L=read()+1,l=1,r=1; REP(i,1,n) c[i]=read()+c[i-1]; REP(i,0,n) a[i]=c[i]+i; REP(i,0,n) b[i]=a[i]+L;
REP(i,1,n) { while(l<r&&calc(q[l],q[l+1])<2*a[i]) l++; f[i]=f[q[l]]+(a[i]-b[q[l]])*(a[i]-b[q[l]]); while(l<r&&calc(i,q[r-1])<calc(q[r-1],q[r])) r--; q[++r]=i; }
printf("%lld\n",f[n]); return 0; }
|