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;
}



posted @ 2015-10-30 09:07  orzpps  阅读(175)  评论(0编辑  收藏  举报