luogu3778 商旅 题解

由题意得盈利效率为一个分数,故此题为分数规划。

题目中的盈利效率定义为:环路中的收益/花费的时间,给出的数据是两个集市 \((i,j)\)\(i\) 购买和从 \(j\) 卖出分别的价格和从 \(i\)\(j\) 的时间,并不能直接用于求盈利效率,需要预处理。

\(g(i,j)\) 为从 \(i\) 点买入,在 \(j\) 点卖出任意商品的最大利润。

在读入价格时预处理出每个 \(g(i,j)\) 。注意这里要枚举 \(i\) 的所有入边与 \(j\) 的所有出边,并将其差取最大值。

for(int i=1;i<=n;++i) for(int j=1;j<=k_;++j) {
    scanf("%lld%lld",&b[i][j],&s[i][j]);
    if(b[i][j]==-1) b[i][j]=inf;
    if(s[i][j]==-1) s[i][j]=0;
    // 对于无法购买的两个集市,将其值改为相减为-inf 的值,以免影响后面的运算
}
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) for(int t=1;t<=k_;++t)
    g[i][j]=max(g[i][j],s[j][t]-b[i][t]);

\(d(i,j)\) 为从 \(i\)\(j\) 所用的最短时间,初始化为正无穷,读入所有两个点距离之后进行 floyd 算法求出。 这里用memset(d,0x3f,sizeof(d))的话只有 82pts,血的教训。

for(int i=1;i<=m;++i) {
    int x, y, z; scanf("%lld%lld%lld",&x,&y,&z);
    d[x][y]=z;
}
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(!d[i][j]) d[i][j]=inf;
for(int k=1;k<=n;++k) for(int i=1;i<=n;++i) for(int j=1;j<=n;++j)
    if(d[i][j]>d[i][k]+d[k][j]) d[i][j]=d[i][k]+d[k][j];

设计完状态之后,就可以列出盈利效率的式子了。

不难想到,二分一个 \(mid\),表示盈利效率为 \(mid\),设 \(i,j\) 为环路上可以相互到达的点 ,\(V\) 为环路点集。

易得下式 \[ \frac{ \sum \limits_{i,j \in V} g(i,j) }{ \sum \limits_{i,j \in V} d(i,j) } \ge mid \]

但是如此并不能直接进行计算,要把它转化为对环的判断

所以我们设建一张新图,设 \(f(i,j)\) 为新图上的权值。

不难得到 \[ \sum \limits_{i,j \in V} g(i,j) \ge mid \times \sum \limits_{i,j \in V} d(i,j) \]

\[ \sum \limits_{i,j \in V} g(i,j) - mid \times \sum \limits_{i,j \in V} d(i,j) \ge 0 \]

所以我们令 \[ \sum \limits_{i,j \in V} f(i,j)= \sum \limits_{i,j \in V} g(i,j) - mid \times \sum \limits_{i,j \in V} d(i,j) \]

所以 \[ f(i,j)=g(i,j)-mid \times d(i,j) \quad i,j \in V \] 然后跑 \(\text{Floyd}\) 算法,判断有无环并取最大值,再判断最大值是否大于等于 0。

bool check(int x) {
    int ans=-inf;
    for(int i=1;i<=n;++i) for(int j=1;j<=n;++j)
        if(i==j) f[i][j]=-inf; else f[i][j]=g[i][j]-x*d[i][j];
    for(int k=1;k<=n;++k) for(int i=1;i<=n;++i) for(int j=1;j<=n;++j)
        f[i][j]=max(f[i][j],f[i][k]+f[k][j]);
    for(int i=1;i<=n;++i) ans=max(ans,f[i][i]);
    // i 到达 i,表示有环。
    // 因为初始 f[i][i] 设置的是-inf 所以不影响答案
    return ans>=0;
}

二分过程。其中初始 \(l=0\)\(r=\max{\{ g(i,j) \}}\)

while(l<r) {
	int mid=(l+r+1)/2;
	if(check(mid)) l=mid; else r=mid-1;
}
printf("%lld",l);

几个细节。

  • 值域是 \([0,10^9]\),对题目中的“如果没有任何一条环路可以盈利,则输出 0”并不需要特判。
  • 一定要开 long long!
  • 因为我们对权的处理,并不需要除法,因此可以完全无视题目中的向下取整。

luogu3778 商旅 题解
https://yozora0908.github.io/2021/lg3778-solution/
作者
yozora0908
发布于
2021年8月1日
许可协议