挺有意思的一道题,我想按我的思路过程来讲一下,看到比值,首先可以想到用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); }