题解 P3745 【[六省联考2017]期末考试】【三分+前缀和】

wjyyy

2018-12-11 21:18:25

Solution

稍微分析了一下三分的正确性,并用三分+前缀和的方式把$O(m\log m)$优化到了$O(m+\log m)$,表示瓶颈在于输入…… **[博客传送门](https://www.wjyyy.top/2818.html)** ## 题解: 首先分析一下本题的模型。可以想象成一个木桶效应,即“**短板**”的来源。 ![img](http://www.wjyyy.top/wp-content/uploads/2018/12/201812091729.png) 如图,即使最快改完卷子的课程用时为$0$,它也需要等待最慢改完卷子的课程。因此在改卷的人力定下来之后,**全部课程**改卷结束的时间是**唯一**的。既然不愉快度是由人力决定的,人力的改变也决定了完成的时间。 因此我们尝试确定一个结束时间,这样再贪心地分配人力,就可以求出以这个时间结束的最小不愉快度。 ![img](http://www.wjyyy.top/wp-content/uploads/2018/12/201812091902.png) 虚线此时是**要求改完卷子的时间**,那么在虚线右边的时间就需要通过调度老师或者增加老师来弥补。首先判断是否有$A\le B$,如果是,则虚线左边的空隙都可以拿来填补虚线右边的时间条,剩下的(若$A>B$,则剩下的就还是原来那么多)需要拿$B\times$天数来弥补。再根据学生的需要,把虚线左边的橙色线条到虚线的距离和都统计出来,乘上$C$,就是我们把结束时间定在虚线位置的不愉快度。 ![img](http://www.wjyyy.top/wp-content/uploads/2018/12/201812091911.png) 我们知道了这些,实际上就可以做这个题了,用两部分前缀和预处理一下,这个时间就可以从$max\{b_i\}$向前枚举了。不过害怕前缀和出问题的话,可以想想三分,此时要证不愉快度是单峰的。~~三分好像不比前缀和好写到哪去。~~ 假设我们现在有最优解$x=t$,那么对于$x+a(a>0)$来说,一定有$ans_{x+a}>ans_x$。 理由是随着改卷结束时间(虚线)的增加,出现在虚线以左的橙线会在个数和距离上都不断增加,尽管由$A,B$产生的不愉快度会减小,但只是杯水车薪,况且这个减小的幅度也会越来越小,最后趋近于$0$。可能会有人担心这个不愉快度减小得比$C$部分增加得快,如果存在某解比最优解更优,它一定与最优解相邻,既然相邻就会一定被三分过程更新到。 对于$x-a$同理,$A,B$产生贡献增加的幅度会越来越大,而$C$产生的贡献幅度会越来越小。因此三分的正确性可以保证。 那么我们就有了$O(m\log b_i)$的优秀三分解法。再在三分内部使用前缀和,就可以做到$O(m+\log b_i)$了~~好神经啊~~。 注意要特判$C_i=10^{16}$的情况,这种情况就算全部补充老师也比让学生不满意优,因此直接贪心决策怎样调度老师。 ## Code: ```cpp #include<cstdio> #include<cstring> #define ll long long #define N 100005 ll read() { ll x=0; char ch=getchar(); while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x; } ll b[N],sum[N];//有多少人期望在第i天之前出成绩 ll sum1[N];//前面人的天数之和 ll Sum[N],Sum1[N];//这里维护的是出成绩时间前缀和 ll x,y,z,n,m; ll f[N];//记忆化在第i天出成绩的代价(减小三分常数) ll calc(int u)//要求在第u天出成绩 { if(f[u]!=-1) return f[u]; f[u]=0; ll r=0,t=0;//r表示有多少余力 t表示还需要多少 r=Sum[u]*u-Sum1[u]; t=Sum1[N-1]-Sum1[u]-(Sum[N-1]-Sum[u])*u; //↑O(1) ↓O(m) /*for(int i=1;i<=m;++i) if(b[i]>u) t+=b[i]-u; else r+=u-b[i]; */ //首先要填满 贪心看是调度便宜还是直接加便宜 if(x<y) { ll tmp=r<t?r:t; f[u]+=tmp*x; t-=tmp; } f[u]+=t*y; f[u]+=(sum[u]*u-sum1[u])*z; return f[u]; } int main() { memset(f,-1,sizeof(f)); x=read();y=read();z=read();n=read();m=read(); int u,mn=N; for(int i=1;i<=n;++i) { u=read(); ++sum[u]; mn=mn<u?mn:u; } for(int i=1;i<N;++i) { sum1[i]=sum1[i-1]+sum[i]*i; sum[i]+=sum[i-1]; } for(int i=1;i<=m;++i) { b[i]=read(); ++Sum[b[i]]; } for(int i=1;i<N;++i) { Sum1[i]=Sum1[i-1]+Sum[i]*i; Sum[i]+=Sum[i-1]; } int l=0,r=N-1,mid; if(z==10000000000000000) r=mn; while(l<r) { mid=(l+r)>>1; ll y_=calc(mid),y__=calc(mid+1); if(y_==y__) { printf("%lld\n",y_); return 0; } else if(y_>y__) l=mid+1; else r=mid; } printf("%lld\n",calc(l)); return 0; } ```