CF643C Levels and Regions
题面
把 n 关,划分成 m 段,记 i 所属段左端点为 li。第 i 关卡用刚好一小时过关概率是 ∑j=liiajai。求通关时打关总次数期望。
n≤2×105,m≤50。
题解
通过一关的时间的期望:
E=yx⋅1+yy−x⋅yx⋅2+(yy−x)2⋅yx⋅3+⋯=yx[1+2⋅yy−x+3⋅(yy−x)2+4⋅(yy−x)3+⋯]
中括号里面是带系数的等比数列求和的形式:
k=2∑∞k⋅pk−1=k=1∑∞pk+l=1∑∞k=l∑∞pk=1−pp+l=1∑∞1−ppl=1−pp+(1−p)2p=(1−p)2p(2−p)
p=yy−x=1−yx,代入:
E=yx[1+2⋅yy−x+3⋅(yy−x)2+4⋅(yy−x)3+⋯]=yx[1+(yx)2(1−yx)(1+yx)]=yx+xy[1−(yx)2]=yx+xy−yx=xy
到这里我们证得,通过一关的时间期望为 ai∑j=liiaj。
开个前缀和 s,通过一关的期望为 aisi−sli。通过一段(左闭右开)关时间期望为:
j=li∑riajsj−sli=j=li∑ri(ajsj−sli⋅aj1)=j=li∑riajsj−slij=li∑riaj1
显然两个都可以前缀和,记 sai=∑j=1iajsj,sbi=∑j=1iaj1。
若确定了一个段,整段的期望时间可以 O(1) 求出。设 fi,j 表示考虑前 i 关,已经分出了 j 段的最优时间。转移方程为:
fi,j=k=1mini−1(fk,j−1+sai−sak+sbk⋅sk−sbi⋅sk)
把只和 i 有关的提出来,为:
fi,j=sai+k=1mini−1(fk,j−1−sak+sbk⋅sk−sbi⋅sk)
这东西显然是斜率优化。虽然被线性做法吊打但我还是要写李超。考虑李超树优化。把 −sk 当作斜率,fk,j−1−sak+sbk⋅sk 当作截距。查询这个东西的最小值就是查询 x=sbi 和这些直线交点的最小值。
注意精度,动态开点的李超线段树存在较大的精度误差。考虑不动态开点,DP 转移中,fi,j 只由 fk,j−1 的转移过来。把李超树滚动一下就能过空间限制了。
代码
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| #include<cstdio> #include<algorithm> #define int long long using namespace std; const int max_n=1000005,inf=1000000000000000LL; inline int read(){ int x=0;bool w=0;char c=getchar(); while(c<'0' || c>'9') w|=c=='-',c=getchar(); while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return w?-x:x; } inline int min(int a,int b){return a<b?a:b;}
int n,m,a[max_n],s[max_n];
double s1[max_n],s2[max_n];
struct LiChao{ int p[max_n*4]; #define ls(x) (x<<1) #define rs(x) (x<<1|1)
double b[max_n],k[max_n];
inline double slp(int j,int i){return b[i]+s2[j]*k[i];}
inline void add(int x,int l,int r,int t){ if(l==r){ if(slp(l,t)<slp(l,p[x])) p[x]=t; return; } int mid=(l+r)>>1; if(slp(mid,t)<slp(mid,p[x])) t^=p[x]^=t^=p[x]; if(slp(l,t)<slp(l,p[x])) add(ls(x),l,mid,t); else if(slp(r,t)<slp(r,p[x])) add(rs(x),mid+1,r,t); } inline double ask(int x,int l,int r,int t){ if(!x) return 0; double res=slp(t,p[x]); if(l==r) return res; int mid=(l+r)>>1; if(t<=mid) return min(res,ask(ls(x),l,mid,t)); return min(res,ask(rs(x),mid+1,r,t)); } inline void clear(){ for(register int i=0;i<=n;++i) k[i]=b[i]=p[i]=0; } #undef ls #undef rs }seg[2];
double f[max_n][2]; double c[max_n]; int pos[max_n];
signed main(){ n=read(),m=read(); for(register int i=1;i<=n;++i){ a[i]=read(); s[i]=s[i-1]+a[i]; s1[i]=1.0*s[i]/a[i], s2[i]=1.0/a[i]; } for(register int i=1;i<=n;++i){ s1[i]+=s1[i-1], s2[i]+=s2[i-1]; c[i]=s2[i]; } sort(s2+1,s2+1+n); for(register int i=1;i<=n;++i) f[i][0]=inf; for(register int i=1;i<=n;++i) pos[i]=lower_bound(s2+1,s2+1+n,c[i])-s2; for(register int j=1,now=1;j<=m;++j,now^=1){ seg[now].clear(); for(register int i=1;i<=n;++i){ f[i][now]=s1[i]+seg[now].ask(1,1,n,pos[i]);
seg[now].k[i]=-1.0*s[i]; seg[now].b[i]=s2[i]*s[i]-s1[i]+f[i][now^1]; seg[now].add(1,1,n,i); } } printf("%.10lf",f[n][m&1]); return 0; }
|