【BZOJ4898】【APIO2017】商旅

本文介绍了一种解决特定问题的方法,该问题涉及到分数规划与图论中的非负环检测。通过对物品交易过程进行数学抽象,利用二分查找和图算法来高效求解最大收益。文章详细阐述了算法步骤,并提供了完整的代码实现。

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

【题目链接】

【思路要点】

  • 明显的分数规划,第一步先二分答案\(Ans\)。
  • 接下来,有一种显然的想法是拆点,把在每一个点时,持有某个物品的状态拆成\(K\)个点,然后,问题转化为了图中是否存在非负环。
  • 但这个做法复杂度是\(O((N+M)KLogS)\)的,(应该)无法通过。
  • 进一步考虑,实际上,我们并不需要记录当前持有什么物品,如果一次买卖的买入市场和卖出市场都已经确定,那么这次买卖的最大获利是可以在\(O(K)\)的时间内计算的(如果没有可以盈利的购买方案,记最大获利为0)。
  • 记从点\(i\)到点\(j\)的最短路长度为\(cost_{i,j}\),确定一次买卖的买入市场和卖出市场分别为\(i\),\(j\)时的最大获利为\(gain_{i,j}\),那么我们只要在\((i,j)\)建立权值为\(gain_{i,j}-Ans*cost_{i,j}\)的边,判断图中是否存在非负环即可。
  • 时间复杂度可近似认为为\(O(N^3+N^2K+N^2LogS)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
const int MAXK = 1005;
const int INF = 1e9;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, k, now;
int b[MAXN][MAXK], s[MAXN][MAXK];
int gain[MAXN][MAXN];
int cost[MAXN][MAXN];
long long dist[MAXN];
bool inq[MAXN], found;
void work(int pos) {
	if (inq[pos]) found = true;
	if (found) return;
	inq[pos] = true;
	for (int i = 1; i <= n; i++)
		if (i != pos && dist[pos] + gain[pos][i] - 1ll * now * cost[pos][i] >= dist[i]) {
			dist[i] = dist[pos] + gain[pos][i] - 1ll * now * cost[pos][i];
			work(i);
		}
	inq[pos] = false;
}
bool check() {
	found = false;
	memset(dist, 0, sizeof(dist));
	for (int i = 1; i <= n; i++)
		work(i);
	return found;
}
int main() {
	read(n), read(m), read(k);
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++)
		if (i == j) cost[i][j] = 0;
		else cost[i][j] = INF;
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= k; j++)
		read(b[i][j]), read(s[i][j]);
	for (int i = 1; i <= m; i++) {
		int x, y, z;
		read(x), read(y), read(z);
		chkmin(cost[x][y], z);
	}
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++)
	for (int k = 1; k <= n; k++)
		chkmin(cost[j][k], cost[j][i] + cost[i][k]);
	int l = 0, r = 0;
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++) {
		int res = 0;
		for (int l = 1; l <= k; l++)
			if (b[i][l] != -1 && s[j][l] != -1) chkmax(res, s[j][l] - b[i][l]);
		gain[i][j] = res;
		chkmax(r, res);
	}
	while (l < r) {
		now = (l + r + 1) / 2;
		if (check()) l = now;
		else r = now - 1;
	}
	writeln(l);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值