幻想乡三连C:狂飙突进的幻想乡

题解:

不难发现,对于每一条从$S$到$T$的路径,设其$x、y$的和为$S_x、S_y$,其对答案的贡献是$a\cdot S_x+(1-a)\cdot S_y$,这是一个关于$a$的一次函数。而所有的路径就对应着许多$a\in [0,1]$直线,而不同$a$所对应的最短路长度恰好构成了这些直线的上凸壳,而求最短路的期望就是求上凸壳的积分(与坐标系横轴所夹面积),考虑到$n、m$很小,所以直接用一次函数不断拟合,每次求点值暴力跑一边最短路即可。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define LL long long
#define M 802
#define N 202
#define INF 1010000000
#define eps (1e-5)
using namespace std;
int read(){
	int nm=0,fh=1; char cw=getchar();
	for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
	for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
	return nm*fh;
}
struct STA{
	int nd; double dst;
	STA();
	STA(int _nd,int _dst){nd=_nd,dst=_dst;}
	bool operator <(const STA&ot)const{return dst>ot.dst;}
};
priority_queue<STA> Q; 
int n,m,X[M],Y[M],fs[N],nt[M],to[M],tmp,S,T;
double dis[N];
bool vis[N];
void link(int x,int y,int t1,int t2){nt[tmp]=fs[x],fs[x]=tmp,to[tmp]=y,X[tmp]=t1,Y[tmp++]=t2;}
double DJ(double V){
    for(int i=1;i<=n;i++) dis[i]=INF*1.0,vis[i]=false;
	while(!Q.empty()) Q.pop(); dis[S]=0.0,Q.push(STA(S,0.0));
	while(!Q.empty()){
		int x=Q.top().nd; Q.pop();
		if(vis[x]) continue; vis[x]=true;
		for(int i=fs[x];i!=-1;i=nt[i]){
			double dt=dis[x]+(X[i]*V)+(Y[i]*(1.0-V));
			if(dt>=dis[to[i]]) continue;
			dis[to[i]]=dt,Q.push(STA(to[i],dt));
		}
	}
	return dis[T];
}
double calc(double L,double R){
	double mid=(L+R)/2.0,ans,ls,rs;
	ans=DJ(mid),ls=DJ(L),rs=DJ(R);
	if(fabs(ans-(ls+rs)/2.0)*(R-L)<eps) return ans*(R-L);
	return calc(L,mid)+calc(mid,R);
}
int main(){
	n=read(),m=read(),S=read(),T=read(),memset(fs,-1,sizeof(fs));
	for(int i=1;i<=m;i++){
		int u=read(),v=read(),t1=read(),t2=read();
		link(u,v,t1,t2),link(v,u,t1,t2);
	}
	printf("%.10f\n",calc(0.0,1.0)); return 0;
}

 

posted @ 2018-09-15 18:38  OYJason  阅读(270)  评论(6编辑  收藏  举报