把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷3778】[APIO2017] 商旅(分数规划+Floyd)

点此看题面

  • 给定一张\(n\)个点\(m\)条边的有向图,有\(k\)种商品,每个点会给出每种商品买进/卖出的价格(若无法买进/卖出则对应价格为\(-1\))。
  • 定义一条环路为带着一个最多容纳一个商品的空包从某个点出发,走过若干点最终回到出发点,在每个点都可以选择买进/卖出商品。
  • 定义一条环路的效率为走一次这条环路能获得的最大价值除以环路长度,求所有环路中最高的效率。
  • \(n\le100,m\le9900,k\le1000\)

分数规划

这种问题一看就非常分数规划,因此直接去二分一个答案\(x\)

那么价值\(C\)除以长度\(L\)大于等于\(x\),可变形发现\(\frac CL\ge x\Leftrightarrow C-x\times L\ge 0\)

建出新图

考虑我们在原图基础上建出一张新图,任意两点之间边的长度为它们的最短路长度(可以\(Floyd\)预处理)。

这样一来,我们就可以强制每次买了一个商品,必须要在下一个点卖出。

因此二分答案之后,对于任意两点我们可以枚举买卖哪种商品,求出最大的获利\(C_{i,j}\),然后在这两点之间连一条边权为\(C_{i,j}-x\times L_{i,j}\)的边。

接下来的问题就是判是否存在非负环,仍然可以\(Floyd\),于是就结束了。

代码:\(O(n^2klogV)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define K 1000
#define DB double
using namespace std;
int n,m,k,p[N+5][K+5],q[N+5][K+5],f[N+5][N+5];
long long g[N+5][N+5];I bool Check(CI x)//检验答案
{
	RI i,j,o,t;for(i=1;i<=n;++i) for(j=1;j<=n;++j)
	{
		if(i==j) {g[i][j]=-1e18;continue;}//自环赋一个极小权值
		for(t=0,o=1;o<=k;++o) ~p[i][o]&&~q[j][o]&&(t=max(t,q[j][o]-p[i][o]));g[i][j]=t-1LL*f[i][j]*x;//枚举两点间买卖的商品
	}
	for(o=1;o<=n;++o) for(i=1;i<=n;++i) for(j=1;j<=n;++j) g[i][j]=max(g[i][j],g[i][o]+g[o][j]);//Floyd
	for(i=1;i<=n;++i) if(g[i][i]>=0) return 1;return 0;//判非负环
}
int main()
{
	RI i,j,o;for(scanf("%d%d%d",&n,&m,&k),i=1;i<=n;++i) for(j=1;j<=k;++j) scanf("%d%d",p[i]+j,q[i]+j);
	for(i=1;i<=n;++i) for(j=1;j<=n;++j) f[i][j]=i^j?1e9:0;
	RI x,y,z;for(i=1;i<=m;++i) scanf("%d%d%d",&x,&y,&z),f[x][y]=min(f[x][y],z);
	for(o=1;o<=n;++o) for(i=1;i<=n;++i) for(j=1;j<=n;++j) f[i][j]=min(f[i][j],f[i][o]+f[o][j]);//Floyd求出任意两点最短路
	RI l=0,r=1e9,mid;W(l^r) Check(mid=l+r+1>>1)?l=mid:r=mid-1;return printf("%d\n",l),0;//分数规划
}
posted @ 2021-05-09 17:40  TheLostWeak  阅读(48)  评论(0编辑  收藏  举报