bzoj4306: 玩具厂
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4306
思路:首先我们可以发现,只有一个点的w是不确定的
那么我们记录一个cost[i],表示在i处建厂除了n点之外的所有点的运输费用之和。
设w[n]=x,tot为环总长,dist[i]表示Σd[j] (1<=j<=i)
于是每个点建厂的真实费用就是形如kx+cost[i]的形式,k根据题意就是n到i的两条路径长度的乘积
真实的费用cst[i]=(tot-dist[i])*dist[i]*x+cost[i]
暴力求cost是O(n^2)的,TLE不解释
而从cost[i-1]推出cost[i]却是可行的
我们要维护一些东西
设l[j],r[j]为j点到i的两条路径长(反正是个环,l,r换个位置也没关系)
cost[i-1]=Σw[j]*l[j]*r[j]
cost[i]=Σw[j]*(l[j]+d[i])*(r[j]-d[i])
做差可得:cost[i]-cost[i-1]=Σw[j]*r[j]*d[i]-Σw[j]*l[j]*d[i]-Σw[j]*d[i]*d[i]
那么我们先暴力求出1号点的cost[1],只要维护好Σw[j]*r[j],Σw[j]*l[j]和Σw[j]即可O(1)转移(然而细节很多...)
Σw[j]好办,关键是Σw[j]*r[j],Σw[j]*l[j],维护好一个,另一个类似
以Σw[j]*r[j]为例
从i-1到i后,所有点到i的距离都减少(或增加)了,d[i]除了当前点,特殊处理即可。
于是我们就O(n)求出了cost
现在考虑真实的费用cst[i]=(tot-dist[i])*dist[i]*x+cost[i]
当x在一段区间内时,在一个点建厂最优,不就是它对应的直线在所有直线下方吗
先按斜率排序,我们就可以类似斜率优化,得到哪些直线可能是最优的。
于是我们维护一个栈
设l1=stack[top-1],l2=sta[top],l3为要加进来的直线
画图可知那么当l1和l2交点横坐标大于l2和l3交点横坐标,那l2就不可能是最优的,把它弹出去。
最后栈中的直线就是可能是最优的
因为概率是均匀分布的
那么每个点对应直线所占最优区间长度/x的取值范围长度就是它设厂的概率
注意:有重复的直线,他们要平分概率
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const double eps=1e-9; const int maxn=100010; typedef long long ll; using namespace std; struct line{double k,b;int id;}li[maxn],sta[maxn]; int n,same[maxn],to[maxn],top; double sw,rw,lw,A,B,w[maxn],d[maxn],cost[maxn],dist[maxn],tmp,tmp2,tot,p[maxn],res[maxn]; //tot 环的总长 //dist[i] 从1到n方向的dis前缀和 //p每条直线的概率,因为有重复的,所以还不是答案,ans是答案 double inter(line a,line b){return (b.b-a.b)/(a.k-b.k);} bool zero(double x){return fabs(x)<eps;} bool cmp(line a,line b){ if (zero(a.k-b.k)) return a.b<b.b; else return a.k<b.k; } void init(){ scanf("%d%lf%lf",&n,&A,&B); for (int i=1;i<n;i++) scanf("%lf",&w[i]); for (int i=1;i<=n;i++){ scanf("%lf",&d[i]),tot+=d[i]; dist[i]=tot,li[i].id=i; } for (int i=1;i<=n;i++) li[i].k=(dist[i])*(tot-dist[i]); rw=w[1]*tot,sw=w[1]; for (int i=2;i<=n;i++){ tmp+=w[i]*(dist[i]-dist[1])*(tot-dist[i]+dist[1]); rw+=w[i]*(dist[i]-dist[1]); lw+=w[i]*(tot-dist[i]+dist[1]); sw+=w[i]; } li[1].b=tmp; for (int i=2;i<=n;i++){ tmp+=rw*d[i]-lw*d[i]-sw*d[i]*d[i]; li[i].b=tmp; rw+=w[i]*tot,lw-=w[i]*tot; rw-=sw*d[i],lw+=sw*d[i]; } //for (int i=1;i<=n;i++) printf("%.5lf %.5lf\n",li[i].k,li[i].b); } void work(){ sort(li+1,li+1+n,cmp); for (int i=1;i<=n;i++){ if (i==1||!zero(li[i].k-li[i-1].k)){ while (top>1&&inter(li[i],sta[top])>inter(sta[top],sta[top-1])) top--; sta[++top]=li[i]; same[top]=1,to[i]=top; } else{ if (zero(li[i].b-sta[top].b)) same[top]++,to[i]=top; } } double len=B-A; double le=-(1e100),ri; for (int i=top;i;i--){ if (i==1) ri=1e100; else ri=inter(sta[i],sta[i-1]); // printf("%.3lf\n",ri); if (zero(len)){if (le<=A&&ri>=B) p[i]=1.0;} else p[i]=max(0.0,min(ri,B)-max(A,le))/len;//确定每条直线的最优区间及长度占比 le=ri; } for (int i=1;i<=n;i++) if (zero(sta[to[i]].b-li[i].b)&&zero(sta[to[i]].k-li[i].k)) res[li[i].id]=p[to[i]]/(1.0*same[to[i]]); for (int i=1;i<=n;i++) printf("%.3lf\n",res[i]); } int main(){ //freopen("t1.in","r",stdin);freopen("t1.out","w",stdout); init(),work(); return 0; }