Problem
Description
小澳最喜欢的歌曲就是《葫芦娃》。
一日表演唱歌,他尽了洪荒之力,唱响心中圣歌。
随之,小澳进入了葫芦世界。
葫芦世界有n个葫芦,标号为1~ n。n个葫芦由m条藤连接,每条藤连接了两个葫芦,这些藤构成了一张有向无环图。小澳爬过每条藤都会消耗一定的能量。
小澳站在1号葫芦上(你可以认为葫芦非常大,可以承受小澳的体重),他想沿着藤爬到n号葫芦上,其中每个葫芦只经过一次。
小澳找到一条路径,使得消耗的能量与经过的葫芦数的比值最小。
Input
输入文件名为calabash.in。
输入文件第一行两个正整数n,m,分别表示葫芦的个数和藤数。
接下来m行,每行三个正整数u,v,w,描述一条藤,表示这条藤由u连向v,小澳爬过这条藤需要消耗w点能量。
Output
输出文件名为calabash.out。
一行一个实数,表示答案(绝对误差不超过 10^-3)。
Sample Input
4 6
1 2 1
2 4 6
1 3 2
3 4 4
2 3 3
1 4 8
Sample Output
2.000
Hint
有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。
考试的时候我发现我好像不太会什么最短路和什么搜索,我就去一个一个的写子任务了。在看到第2、6个点除1外,所有的葫芦入度均为1,我就直接按照链来写了。。。交完我才发现出度可以不为1啊
接下来是正解部分:
我们求助了某2位不愿透漏姓名的大佬。
他说这道题可以用spfa做,对于每个点都存一下每条经过它的路径到这个点所经过的点数和距离,逐次更新经过这个点时两者的最优比值。最后输出经过n时的结果就可以。
大佬1代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
typedef double D;
#define ms(a,b) memset(a,b,sizeof(a))
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define pr pair<D,int>
#define mp make_pair
#define inf 2147483647
#define sc second
int read() {
int as = 0,fu = 1;
char c = getchar();
while(c < '0' || c > '9') {
if(c == '-') fu = -1;
c = getchar();
}
while(c >= '0' && c <= '9') {
as = as * 10 + c - '0';
c = getchar();
}
return as * fu;
}
const int N = 2005;
//head
int n,m;
int head[205],nxt[N],cst[N],mo[N],cnt;
inline void add(int x,int y,int z) {
mo[++cnt] = y;
cst[cnt] = z;
nxt[cnt] = head[x];
head[x] = cnt;
return;
}
int dep[205],val[205];
D dis[205];
bool vis[205];
void spfa() {
priority_queue<pr,vector<pr> ,greater<pr> > q;
ms(dep,0);
ms(val,0);
dep[1] = 1;
q.push(mp(0.0,1));
while(!q.empty()) {
int x = q.top().sc;
q.pop();
vis[x] = 0;
// printf("%d\n",x);
for(int i = head[x];i;i = nxt[i]) {
int sn = mo[i];
// printf("%d %d\n",x,sn);
int d = dep[x] + 1;
int v = val[x] + cst[i];
if((dep[sn] == 0 && val[sn] == 0) || (D)v/(D)d < (D)val[sn]/dep[sn]) {
// printf("%.3lf\n",(D)v/(D)d);
val[sn] = v,dep[sn] = d;
if(vis[sn]) continue;
vis[sn] = 1;
q.push(mp((D)v/(D)d,sn));
}
}
}
// rep(i,1,n) printf("%d %d %d\n",i,val[i],dep[i]);
printf("%.3lf",(D)val[n]/(D)dep[n]);
}
int main() {
freopen("calabash.in","r",stdin);
freopen("calabash.out","w",stdout);
n = read();
m = read();
while(m--) {
int x = read();
int y = read();
int z = read();
add(x,y,z);
}
spfa();
return 0;
}
鉴于我是一个只会dijisktra的弱鸡,代码我就不写了,等日后学会了再来填坑。
还可以用动态规划,开一个二位数组,第一维存这个点的编号,第二维存到这个点经过的点数,数组的值为经过的距离。然后最后就可以求出来了
大佬2代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<string>
#include<iostream>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<vector>
using namespace std;
typedef long long ll;
int vis[2005], go[2005], next[2005], head[2005], dp[205][205],f[40005][2];
int q, m, n, t, w = 1, now, num;
double ans = 0x3f3f3f3f3f;
bool flag[205][205];
int read()
{
int ans = 0,op = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-') op = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
ans *= 10;
ans += ch - '0';
ch = getchar();
}
return ans * op;
}
void add(int x,int y,int z)
{
go[++q] = y;
next[q] = head[x];
head[x] = q;
vis[q] = z;
}
int main()
{
freopen("calabash.in", "r", stdin);
freopen("calabash.out", "w", stdout);
memset(dp,127,sizeof(dp));
dp[1][1]=0;
f[1][0] = f[1][1] = 1;
n = read();
m = read();
for(int i = 1;i <= m;i++)
{
int a, b, c;
a = read();
b = read();
c = read();
add(a, b, c);
}
while(t < w)
{
t++;
now = f[t][0];
num = f[t][1];
for(int i = head[now];i;i = next[i])
{
int reach = go[i];
if (dp[now][num] + vis[i] < dp[reach][num+1])
{
dp[reach][num+1] = dp[now][num] + vis[i];
if (!flag[reach][num+1])
{
f[++w][0] = reach;
f[w][1] = num + 1;
flag[reach][num+1] = 1;
}
}
}
flag[now][num] = 0;
}
for(int i = 2;i <= n;i++)
{
if((double)dp[n][i] / i < ans) ans = (double)dp[n][i] / i;
}
printf("%.3lf\n", ans);
return 0;
}