题解:P17737 [ICPC 2019 Xuzhou R] Factory
先把“每种饮料都指定一台机器,整升交给它生产”称为一个方案。记方案
虽然一个方案不拆分饮料,但按比例混合多个方案,就可以表示任意连续生产安排。具体地,设第
现在换一个角度:在
如果完整问题的最优值为
假设已经用单纯形法求出了当前这些方案的最优值
接下来寻找加权耗时最小的方案。各饮料的选择相互独立,第
若
更关键的是,这次扫描还给出了答案的下界,而且不必依赖对偶问题已经被精确求解。对于任意非负权值
所以
为减轻大量相同耗时引起的浮点退化,代码把机器约束的右端项从
当
这就满足题目的相对误差要求,并留有一定的数值误差余量。
设总共额外加入
下面是完整的 C++17 实现。单纯形表的前
#include <bits/stdc++.h>
using namespace std;
struct Simplex {
static constexpr double EPS = 1e-7;
int k;
vector<vector<double>> a;
vector<double> b, c;
double value = 0;
// 前 k 列是原始松弛变量,后面是已经加入的生产方案。
Simplex(int n, const vector<double>& sum) : k(sum.size()),
a(k, vector<double>(2 * k)), b(k), c(2 * k) {
for (int i = 0; i < k; ++i) {
b[i] = n / sum[i];
a[i][i] = b[i];
// 对机器容量加入微小扰动;最终计算上界时会补偿。
b[i] *= 1 + 1e-5 * (i + 1) / k;
a[i][k + i] = 1;
c[i] = -a[i][i];
value += b[i];
}
}
void pivot(int r, int e) {
int m = c.size();
double v = a[r][e];
b[r] /= v;
for (double& x : a[r]) x /= v;
for (int i = 0; i < k; ++i) if (i != r) {
v = a[i][e];
b[i] -= v * b[r];
for (int j = 0; j < m; ++j) {
a[i][j] -= v * a[r][j];
}
a[i][e] = 0;
}
v = c[e];
value += v * b[r];
for (int j = 0; j < m; ++j) {
c[j] -= v * a[r][j];
}
c[e] = 0;
}
// 加入一个方案,重新优化当前有限个方案的线性规划。
void add(const vector<double>& col) {
// 前 k 列保存了基变换,用它们转换新列。
for (int i = 0; i < k; ++i) {
double v = 0;
for (int j = 0; j < k; ++j) {
v += a[i][j] * col[j];
}
a[i].push_back(v);
}
double reduced = 1;
for (int j = 0; j < k; ++j) {
reduced += c[j] * col[j];
}
c.push_back(reduced);
while (true) {
int e = max_element(c.begin(), c.end()) - c.begin();
if (c[e] <= EPS) return;
int r = -1;
for (int i = 0; i < k; ++i) if (a[i][e] > 1e-9) {
if (r == -1) {
r = i;
continue;
}
double u = b[i] / a[i][e];
double v = b[r] / a[r][e];
if (u < v) r = i;
}
pivot(r, e);
}
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
vector<array<unsigned char, 50>> t(n);
vector<double> sum(k, 0);
for (auto& row : t) {
for (int j = 0; j < k; ++j) {
int v;
cin >> v;
row[j] = v;
sum[j] += v;
}
}
Simplex lp(n, sum);
vector<double> y(k), col(k);
while (true) {
double sy = 0;
for (int j = 0; j < k; ++j) {
y[j] = max(0.0, -lp.c[j]);
sy += y[j];
}
fill(col.begin(), col.end(), 0);
// 每种饮料独立选择加权耗时最小的机器。
for (const auto& row : t) {
int p = 0;
double best = row[0] * y[0];
for (int j = 1; j < k; ++j) {
double v = row[j] * y[j];
if (v < best) {
best = v;
p = j;
}
}
col[p] += row[p];
}
double v = 0;
for (int j = 0; j < k; ++j) {
col[j] /= n;
v += col[j] * y[j];
}
// 上界补偿容量扰动,下界始终针对原问题计算。
double upper = (1 + 1e-5) * n / lp.value;
double lower = min(upper, n * v / sy);
if (lower >= 0.982 * upper) {
cout << fixed << setprecision(10)
<< (lower + upper) / 2 << '\n';
return 0;
}
lp.add(col);
}
}