【2018/10/11】T3 - 0/1分数规划 - 葫芦

葫芦

描述

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。

测试点编号nm特殊说明
121
210099除 1 外,所有葫芦的入度均为 1
3100105所有从 1 到 n 的路径经过的葫芦数相等
41001000
51001000
6199198除 1 外,所有葫芦的入度均为 1
7200231所有从 1 到 n 的路径经过的葫芦数相等
82002000
92002000
102002000

对于所有数据,Tom 爬过每条藤消耗的能量不会超过 10^3,且一定存在一条从 1 到 n 的路径。

 

分析

水题*3

很明显啊,一眼分数规划

但是笨笨的我一不小心推错了

看到分数规划,首先想到sigma(ai)/sigma(bi)这个式子

然后将这道题具体的  ai 和 bi 代入进去,得到\frac{w1+w2+....+wn}{n+1}

min(\frac{w1+w2+....+wn}{n+1}) = x

相当于现在就是要求 x ,我们就二分求解,问题就在于如何check了,再转化一下:

w1+w2+....+wn = x*(n+1)

(w1-x)+(w2 - x)+....+(wn-x) = x

现在来二分,得到一个mid,如果(w1-mid)+(w2 - mid)+....+(wn-mid) > mid

那么显然mid还可以调大一些,同理如果(w1-mid)+(w2 - mid)+....+(wn-mid) < mid就说明mid太大了

check的主要部分完了,我们又该怎么判(w1-mid)+(w2 - mid)+....+(wn-mid) ? mid这个关系呢?

根据题意我们需要最小值,那就新建一个图在原图的基础上将边权改为wi-mid,跑一遍最短路就好啦

 

代码

#include<bits/stdc++.h>
#define ll long long
#define in read()
#define N 205
#define M 4005
#define inf 0x3f3f3f3f
using namespace std;
inline int read(){
	char ch;int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<3)+(res<<1)+ch-'0';
		ch=getchar();
	}
	return f==1?res:-res;	
}
int n,m;
int nxt[M],to[M],head[N],cnt=0,from[N];
double w[M];
void add(int x,int y,int z){
	nxt[++cnt]=head[x];head[x]=cnt;
	to[cnt]=y;w[cnt]=z;
}
double wne[M],d[N];
bool vis[N];
void spfa(){
	memset(d,127,sizeof(d));
	queue<int > q;q.push(1);vis[1]=1;d[1]=0;
	while(!q.empty()){
		int u=q.front();q.pop();vis[u]=0;
		for(int e=head[u];e;e=nxt[e])
		{
			int v=to[e];
			if(d[v]>d[u]+wne[e]){
				d[v]=d[u]+wne[e];
				if(!vis[v]){
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
}
bool check(double l){
	for(int i=1;i<=cnt;++i) wne[i]=w[i]-l;
	spfa();
	if(d[n]>l) return true;
	return false;
}
int main(){
	n=in;m=in;int flag=0;
	for(int i=1;i<=m;++i){
		int u,v,w;
		u=in;v=in;w=in;
		add(u,v,w);
	}
	double l=0,r=3000000;
	while(l+1e-10<r){
		double mid=(l+r)/2.0;
		if(check(mid)) l=mid;
		else r=mid;注意实数的二分,不存在加1减1 
	}
	printf("%lf",l);
	return 0;
}

 

 

memset(128)-->极小值

memset(127)->极大值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值