传送门
双倍经验 代码差不多
题意:n个点,m条有向边,每个点有一个乐趣值val,经过每条边花费时间wi,求从某一个点出发最后又回到出发点,使平均每单位时间内获得的乐趣值最大.
分析:设环S={V,E}(V是点集,E是边集).理解题意,我们要求的实际上就是\(\frac{\sum_{i=1}^{k}val[v_i]}{\sum_{i=1}^{k}w[e_i]}\)
令\(x=\frac{\sum_{i=1}^{k}val[v_i]}{\sum_{i=1}^{k}w[e_i]}\)
整理得\(\sum_{i=1}^kval[v_i]-x*\sum_{i=1}^{k}w[e_i]=0\)
继续得\(\sum_{i=1}^k(val[v_i]-x*w[e_i])=0\)
令\(f(x)=\sum_{i=1}^k(val[v_i]-x*w[e_i])\)
f(x)随x增大而减小,即f(x)是一个递减函数,既然具有单调性,就要想到二分答案.
我们直接二分要求的答案x,check时把每条边的边权看作\(val[v_i]-x*w[e_i]\)如果此次二分的值合法,即\(\frac{\sum_{i=1}^kval[v_i]}{\sum_{i=1}^{k}w[e_i]}>x\),根据上面的推导得\(\sum_{i=1}^k(val[v_i]-x*w[e_i])>0\),即图中存在一个正环.
我们一般都是求负环吧,所以我们稍稍转换一下,把\(\sum_{i=1}^k(val[v_i]-x*w[e_i])>0\)两边同时乘上-1,变成了\(\sum_{i=1}^k(-val[v_i]+x*w[e_i])<0\)
所以本题就转换为了二分x,把每条边权看作\(-val[v_i]+x*w[e_i]\),然后判断图中是否存在负环.
我写的是dfs+spfa判负环(如果不会判负环,请看广告).
注意本题是实数域上的二分答案.
int n,m,tot,visit[1005],val[1005];
int head[1005],nxt[5005],to[5005],w[5005];
double eps=1e-4,dis[1005];
void add(int a,int b,int c){
nxt[++tot]=head[a];
head[a]=tot;
to[tot]=b;
w[tot]=c;
}
bool dfs_spfa(int x,double mid){
visit[x]=1;
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
if(dis[y]>dis[x]-val[x]+mid*w[i]){
dis[y]=dis[x]-val[x]+mid*w[i];
if(visit[y]||dfs_spfa(y,mid))
return 1;
}
}
visit[x]=0;
return 0;
}
bool check(double mid){
memset(visit,0,sizeof(visit));
for(int i=1;i<=n;i++)dis[i]=0;
for(int i=1;i<=n;i++)
if(dfs_spfa(i,mid))return 1;
return 0;
}
int main(){
n=read();m=read();
for(int i=1;i<=n;i++)val[i]=read();
for(int i=1;i<=m;i++){
int a=read(),b=read(),c=read();
add(a,b,c);
}
double l=-1e9,r=1e9,mid;
while(l+eps<r){
mid=(l+r)/2.0;
if(check(mid))l=mid;
else r=mid;
}
printf("%.2lf\n",l);
return 0;
}