[APIO2017]商旅
Link
Description
一个 \(n\) 个点 \(m\) 条边的有向图。共有 \(K\) 种物品,每个点都有这 \(K\) 种物品,并且每个点的每种物品都有一个买入卖出价格。一个物品可以在一个点买入后,走到另一个点时卖出,且任意时刻只能拥有一个物品。现在要找图上的一个有向环,使得在这个环上进行买卖后得到的利润和环的边的个数的比值最大,求比值。
Solution
这个东西和最优比例生成环很像,但不完全一样,买和卖这个过程很不好处理,因为一定要知道路径是怎样的。考虑最优比例生成环是怎么做的,我们是转为零一分数规划,然后判有没有正环。原图的边可以直接转换,变为 \((u,v,0)\)。仿照上述做法,抽象买和卖这个过程,将其也转换为边。假设在 \(u\) 花 \(x\) 元买入,在 \(v\) 以 \(y\) 元卖出,要使得比例最大,一定要走最短路,记为 \(dis_{u,v}\),如果二分一个 \(mid\) 后,就加入一条 \((u,v,y-x-dis_{u,v}\times mid)\)。最后判有没有正环。
#include<stdio.h>
#include<queue>
#include<cassert>
using namespace std;
#define ll long long
const int N=1e2+3;
const int M=1e3+7;
int n,m,K;
ll a[N][M],b[N][M],mp[N][N],dis[N];
struct E{
int u,v,dis,val;
}e[N*N*16];
struct EE{
int next,to;
ll dis;
}g[N*N*16];
int head[N],cnt=0,in[N];
bool tag[N];
inline void add(int id,int to,ll dis){
g[++cnt]=(EE){head[id],to,dis};
head[id]=cnt;
// printf("%d %d %lld\n",id,to,dis);
}
inline ll min(ll x,ll y){return x<y? x:y;}
inline ll max(ll x,ll y){return x>y? x:y;}
queue<int> Q;
bool check(ll x){
cnt=0; for(int i=1;i<=n;i++) head[i]=0;
for(int i=1;i<=m;i++)
add(e[i].u,e[i].v,1ll*e[i].val-e[i].dis*x);
while(!Q.empty()) Q.pop();
for(int I=1;I<=n;I++){
for(int i=1;i<=n;i++)
dis[i]=in[i]=tag[i]=0;
Q.push(I); in[I]=tag[I]=1;
while(!Q.empty()){
int u=Q.front(); Q.pop(); tag[u]=0;
for(int i=head[u];i;i=g[i].next){
int v=g[i].to;
if(dis[v]<=dis[u]+g[i].dis){
dis[v]=dis[u]+g[i].dis;
if((in[v]=in[u]+1)>n) return 1;
if(!tag[v]) tag[v]=1,Q.push(v);
}
}
}
}
return 0;
}
int main(){
scanf("%d%d%d",&n,&m,&K);
for(int i=1;i<=n;i++)
for(int j=1;j<=K;j++)
scanf("%lld%lld",&a[i][j],&b[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=j) mp[i][j]=2e9;
for(int i=1,x,y,D;i<=m;i++){
scanf("%d%d%d",&x,&y,&D);
e[i]=(E){x,y,D,0};
mp[x][y]=min(mp[x][y],D);
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
if(mp[i][j]==2e9||i==j) continue;
int ret=-1;
for(int k=1;k<=K;k++)
if((a[i][k]!=-1)&&(b[j][k]!=-1)&&a[i][k]<b[j][k])
ret=max(ret,b[j][k]-a[i][k]);
// assert(ret>=-1);
if(~ret) e[++m]=(E){i,j,mp[i][j],ret};
}
// printf("----\n");
// for(int i=1;i<=m;i++) printf("%d %d %d %d\n",e[i].u,e[i].v,e[i].dis,e[i].val);
ll lf=0,rf=1e9,ans=0;
while(lf<=rf){
ll mid=(lf+rf)>>1;
if(check(mid)) ans=mid,lf=mid+1;
else rf=mid-1;
}
printf("%lld",ans);
}