bzoj 4898 [Apio2017]商旅

本文介绍了一种算法,用于解决在一个具有多种商品交易和单向道路连接的城市中找到盈利效率最高的环路的问题。通过预处理最大收益路径和使用SPFA算法检查是否存在正环来确定最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

在广阔的澳大利亚内陆地区长途跋涉后,你孤身一人带着一个背包来到了科巴。你被这个城市发达而美丽的市场所深深吸引,决定定居于此,做一个商人。科巴有个集市,集市用从1到N的整数编号,集市之间通过M条单向道路连接,通过每条道路都需要消耗一定的时间。在科巴的集市上,有K种不同的商品,商品用从1到K的整数编号。每个集市对每种商品都有自己的定价,买入和卖出商品的价格可以是不同的。并非每个集市都可以买卖所有的商品:一个集市可能只提供部分商品的双向交易服务;对于一种商品,一个集市也可能只收购而不卖出该商品或只卖出而不收购该商品。如果一个集市收购一种商品,它收购这种商品的数量是不限的,同样,一个集市如果卖出一种商品,则它卖出这种商品的数量也是不限的。为了更快地获得收益,你决定寻找一条盈利效率最高的环路。环路是指带着空的背包从一个集市出发,沿着道路前进,经过若干个市场并最终回到出发点。在环路中,允许多次经过同一个集市或同一条道路。在经过集市时,你可以购买或者卖出商品,一旦你购买了一个商品,你需要把它装在背包里带走。由于你的背包非常小,任何时候你最多只能持有一个商品。在购买一个商品时,你不需要考虑你是否有足够的金钱,但在卖出时,需要注意只能卖出你拥有的商品。从环路中得到的收益为在环路中卖出商品得到的金钱减去购买商品花费的金钱,而一条环路上消耗的时间则是依次通过环路上所有道路所需要花费的时间的总和。环路的盈利效率是指从环路中得到的收益除以花费的时间。需要注意的是,一条没有任何交易的环路的盈利效率为0。你需要求出所有消耗时间为正数的环路中,盈利效率最高的环路的盈利效率。答案向下取整保留到整数。如果没有任何一条环路可以盈利,则输出0。


      挺有意思的一道题,我想按我的思路过程来讲一下,看到比值,首先可以想到用01分数规划的方法,二分答案的比值。但是这个比值由于走路和买东西是独立的,并不能直接做w1[i]=w[i]-mid*t[i]的转化,所以我们可以先找出一些有用的结论。

       我们从一个点走到另一个点(如果走的路是两个点之间的最短路的话),走到的另一个点肯定是要卖出的,所以我们可以用dis[i][j]表示走到第i个点,拥有第j个物品的最大价值,那么走到下一个点,就是将第j个物品卖出,走这一段的代价就是利益-mid*路程。我们只需要判断是否存在正环即可,这时的复杂度是n*n*m*n*logn的,显然我们还需要再优化一维。

       我们有了上面这个结论,我们可以再进一步思考,我们从一个点走到另一个点(如果走的路是两个点之间的最短路的话),我要去这个点,那么意味着我肯定是在前一个点买了东西,然后要在下一个点卖出,而这样的买卖是没有后效性的,所以我们可以预处理一个val[i][j]表示从第i个点到第j个点所能得到的最大收益,那么在状态的表示的时候就可以优化成dp[i]表示走到第i个点的最大价值,这样问题就完美的解决了。

      下附AC代码。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#define maxn 105
using namespace std;
typedef long long ll;
ll n,m,len;
ll d[maxn][maxn];
ll dis[maxn][maxn];
double val[maxn][maxn];
ll buy[maxn][maxn*10],sel[maxn][maxn*10],cnt[maxn],vis[maxn],dis2[maxn];
queue<ll> q;
ll spfa()
{
    while(!q.empty()) q.pop();
    for(ll i=1;i<=n;i++) 
		dis2[i]=vis[i]=cnt[i]=0,q.push(i);
    while(!q.empty())
    {
        ll x=q.front();
		q.pop(),vis[x]=0;
        for(ll i=1;i<=n;i++)
        {
            if(dis2[i]<=dis2[x]+val[x][i])
            {
                dis2[i]=dis2[x]+val[x][i];
                if(!vis[i])
                {
                    if(cnt[i]==n) return 1;
                    cnt[i]++,vis[i]=1,q.push(i);
                }
            }
        }
    }
    return 0;
}
ll check(ll mid)
{
	for(ll i=1;i<=n;i++)
		for(ll j=1;j<=n;j++)
			val[i][j]=dis[i][j]-mid*d[i][j];
	return spfa();
}
int main()
{
	scanf("%lld%lld%lld",&n,&m,&len);
	
	for(ll i=1;i<=n;i++)
		for(ll j=1;j<=len;j++)
			scanf("%lld%lld",&buy[i][j],&sel[i][j]);
		
	memset(d,0x3f,sizeof(d));
	for(ll i=1;i<=m;i++)
	{
		ll x,y,z;
		scanf("%lld%lld%lld",&x,&y,&z);
		d[x][y]=min(d[x][y],z);
	}
	for(ll k=1;k<=n;k++)
	{
		for(ll i=1;i<=n;i++)
		{
			for(ll j=1;j<=n;j++)
			d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
		}
	}
	
	for(ll i=1;i<=n;i++)
	{
		for(ll j=1;j<=n;j++)
		if(i!=j)
		{
			for(ll k=1;k<=len;k++)
			if(sel[j][k]!=-1 && buy[i][k]!=-1)
			{
				dis[i][j]=max(dis[i][j],sel[j][k]-buy[i][k]);
			}
		}
	}

	ll l=0.0,r=1000000000,ans=0.0;
	while(l<=r)
	{
		ll mid=(l+r)/2;
		if(check(mid))
		ans=mid,l=mid+1;
		else r=mid-1; 
	}
	printf("%lld\n",ans);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值