NOIP模拟 葫芦(分数规划)
【题目描述】
Tom 最喜欢的歌曲就是《葫芦娃》。
一日表演唱歌,他尽了洪荒之力,唱响心中圣歌。
随之,Tom 进入了葫芦世界。
葫芦世界有 n 个葫芦,标号为 1~ n。n 个葫芦由 m 条藤连接,每条藤连接了两个葫芦, 这些藤构成了一张有向无环图。Tom 爬过每条藤都会消耗一定的能量。
Tom 站在 1 号葫芦上(你可以认为葫芦非常大,可以承受 Tom 的体重),他想沿着藤爬 到 n 号葫芦上,其中每个葫芦只经过一次。
Tom 找到一条路径,使得消耗的能量与经过的葫芦数的比值最小。
【输入格式】
输入文件第一行两个正整数 n,m,分别表示葫芦的个数和藤数。
接下来 m 行,每行三个正整数 u,v,w,描述一条藤,表示这条藤由 u 连向 v,Tom 爬过 这条藤需要消耗 w 点能量。
【输出格式】
一行一个实数,表示答案(误差不超过 10^-3)。
【样例输入】
4 6
1 2 1
2 4 6
1 3 2
3 4 4
2 3 3
1 4 8
【样例输出】
2.000
【备注】
有 4 种爬法:
1->4,消耗能量 8,经过 2 个葫芦,比值为 8/2=4。
1->2->4,消耗能量 1+6=7,经过 3 个葫芦,比值为 7/3≈2.33。
1->3->4,消耗能量 2+4=6,经过 3 个葫芦,比值为 6/3=2。
1->2->3->4,消耗能量 1+3+4=8,经过 4 个葫芦,比值为 8/4=2。
所以选第三种或第四种方案,答案为 2。
测试点编号 |
n |
m |
特殊说明 |
---|---|---|---|
1 |
2 |
1 |
|
2 |
100 |
99 |
除 1 外,所有葫芦的入度均为 1 |
3 |
100 |
105 |
所有从 1 到 n 的路径经过的葫芦数相等 |
4 |
100 |
1000 |
|
5 |
100 |
1000 |
|
6 |
199 |
198 |
除 1 外,所有葫芦的入度均为 1 |
7 |
200 |
231 |
所有从 1 到 n 的路径经过的葫芦数相等 |
8 |
200 |
2000 |
|
9 |
200 |
2000 |
|
10 |
200 |
2000 |
对于所有数据,Tom 爬过每条藤消耗的能量不会超过 10^3,且一定存在一条从 1 到 n 的路径。
【题目分析】
另建一个起点 0,连接一条 0 到 1 长度为 0 的边,就此将问题转化为长度和 边数最小比值。
这个问题的求解需要分数规划。 假设答案为 ans,对于任意一条由 k 条边组成的路径,有: (w1+w2+w3+…+wk)/k>=ans; 转化一下: (w1+w2+w3+…+wk) >=ans*k; 即(w1-ans)+(w2-ans)+(w3-ans)+…+(wk-ans)>=0。 于是就得到了这样一个算法: 二分答案 x,每次将每一条边的权值减去 x 求最短路,判断 1~n 的最短路是 否大于 0:若大于 0,则说明答案 ans>x;否则说明 ans<x。
【代码~】
#include<bits/stdc++.h>
#define eps 0.0001
using namespace std;
const int N=40000,M=60600;
int tot,que[N],a,b,nxt[M],head[N],vis[M],n,m,dis[M],c;
double pre[M],to[M];
bool edge[N];
void addedge(int a,int b,int c)
{
++tot;
nxt[tot]=head[a];
head[a]=tot;
vis[tot]=b;
dis[tot]=c;
return ;
}
bool spfa(double v)
{
for(int i=2;i<=n;i++)
to[i]=1999999999;
to[1]=0;
for(int i=1;i<=tot;i++)
pre[i]=dis[i]-v;
int he=1,tail=1;
que[he]=1;
edge[1]=true;
while(he<=tail)
{
int u=que[he];
edge[u]=false;
for(int p=head[u],v=vis[p];p;p=nxt[p],v=vis[p])
{
if(to[v]-eps>to[u]+pre[p])
{
to[v]=to[u]+pre[p];
if(!edge[v])
{
que[++tail]=v;
edge[v]=true;
}
}
}
he++;
}
return v-to[n]>eps;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;++i)
{
cin>>a>>b>>c;
addedge(a,b,c);
}
double mid,l=0,r=1e3;
while((r-l)>eps)
{
mid=(l+r)/2.0;
if(spfa(mid))
r=mid;
else l=mid;
}
printf("%0.3lf",l);
return 0;
}