01分数规划
01分数规划属于二分法的一个应用,主要用于解决有关 “最优比率” 的问题,如最优比率背包、最优比率生成树等。
题目大致是说,给定两个长度均为
考虑如何求解。
最暴力的方法就是二进制枚举,复杂度
我们考虑去猜数,猜测所求的值
移项可得:
此时我们要对每一个
此外,当
总时间复杂度
P4377 [USACO18OPEN] Talent Show G
这道题将01分数规划与01背包结合,求
因为
最后如果
代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int kmax = 255;
const int kmaxM = 1005;
struct Cow {
int w, t;
double v;
} c[kmax];
int n, w;
double l, r;
double f[kmaxM];
bool Check(double x) {
for (int i = 1; i <= n; i++) {
c[i].v = c[i].t - x * c[i].w; // 求值
}
f[0] = 0;
for (int i = 1; i <= w; i++) {
f[i] = -1e18;
}
for (int i = 1; i <= n; i++) { // 背包
for (int j = w; ~j; j--) {
f[min(w, j + c[i].w)] = max(f[min(w, j + c[i].w)], f[j] + c[i].v);
}
}
return f[w] < 0;
}
int main() {
cin >> n >> w;
for (int i = 1; i <= n; i++) {
cin >> c[i].w >> c[i].t;
r += c[i].t; // 计算上限
}
for (int i = 1; i <= 60; i++) { //限制二分次数
double mid = (l + r) / 2.0;
if (Check(mid)) {
r = mid;
} else {
l = mid;
}
}
cout << (long long)(1000ll * l);
return 0;
}
P4322 [JSOI2016] 最佳团体
这道题要在01分数规划下做树上背包。
记
注意
代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <vector>
using namespace std;
const int kmax = 2505;
const double eps = 1e-4; // 精度
struct Person {
int c, p;
double v;
} p[kmax];
int m, n;
int siz[kmax];
double l, r;
double f[kmax][kmax];
vector<int> e[kmax];
void Dfs(int x, int fa) {
f[x][1] = p[x].v; // 只选他自己一个人
siz[x] = 1;
for (int y : e[x]) {
if (y == fa) continue;
Dfs(y, x);
for (int i = min(m + 1, siz[x] + siz[y]); i; i--) {
for (int j = 0; j <= min(i - 1, siz[y]); j++) {
f[x][i] = max(f[x][i], f[x][i - j] + f[y][j]);
}
}
siz[x] += siz[y];
}
}
bool Check(double x) {
for (int i = 1; i <= n; i++) {
p[i].v = p[i].p - x * p[i].c;
}
for (int i = 0; i <= n; i++) {
for (int j = 1; j <= m + 1; j++) {
f[i][j] = -1e18;
}
}
Dfs(0, 0);
return f[0][m + 1] <= 0;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> m >> n;
for (int i = 1, x; i <= n; i++) {
cin >> p[i].c >> p[i].p >> x;
e[x].push_back(i);
r += p[i].p;
}
for (double mid; l + eps < r;) {
mid = (l + r) / 2.0;
if (Check(mid)) {
r = mid;
} else {
l = mid;
}
}
cout << fixed << setprecision(3) << l;
return 0;
}
P3199 [HNOI2009] 最小圈
简化题意:给定一张有向图,定义一个环的价值为环上边的边权平均值,求这张图所有环的价值最小值。
假设一个环有
也就是说,将原来每条边的边权减去
这里同样可以采用二分提高效率。
判负环时以每个点为起点,如果对同一点更新了多次答案,说明存在负环。
代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <vector>
using namespace std;
const int kmax = 3005;
const double eps = 1e-9;
struct E {
int y;
double w;
};
int n, m;
double l = -1e6, r = 1e6;
double d[kmax];
bool b[kmax], o;
vector<E> e[kmax];
void Spfa(int x, double k) {
b[x] = 1;
for (auto cur : e[x]) {
int y = cur.y;
double w = cur.w - k;
if (d[y] > d[x] + w) {
d[y] = d[x] + w;
if (b[y]) {
o = 1;
return;
}
Spfa(y, k);
}
}
b[x] = 0;
}
bool Check(double k) {
o = 0;
memset(b, 0, sizeof(b));
memset(d, 0, sizeof(d));
for (int i = 1; i <= n && !o; i++) {
Spfa(i, k);
}
return o;
}
int main() {
cin >> n >> m;
for (int i = 1, x, y; i <= m; i++) {
double w;
cin >> x >> y >> w;
e[x].push_back({y, w});
}
for (double mid; l + eps < r;) {
mid = (l + r) / 2.0;
if (Check(mid)) {
r = mid;
} else {
l = mid;
}
}
cout << fixed << setprecision(8) << r << '\n';
return 0;
}
P3288 [SCOI2014] 方伯伯运椰子
题目中的两种调整方式分别对应退流和增广。最优方案是在最大流不变的情况下使压缩、扩容总费用和最少。
更形式化点说,存在残量网络使得:
- 扩容:
花费 - 压缩:(反向边有流量)满足
时 花费
然后套用分数规划,同上题思路判负环即可。
为什么可行,需要借助一个定理 —— 消圈定理。
在某个流
代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <vector>
using namespace std;
const int kmax = 5005;
const double eps = 1e-4;
struct E {
int y;
double w;
};
int n, m;
vector<E> e[kmax];
bool b[kmax], o;
double d[kmax];
void Spfa(int x, double k) {
b[x] = 1;
for (auto cur : e[x]) {
int y = cur.y;
double w = cur.w + k;
if (d[y] > d[x] + w) {
d[y] = d[x] + w;
if (b[y]) { // 再次被更新
o = 1; // 说明存在负环
return;
}
Spfa(y, k);
}
}
b[x] = 0;
}
bool Check(double k) {
memset(b, 0, sizeof(b));
memset(d, 0, sizeof(d));
o = 0;
for (int i = 1; i <= n && !o; i++) {
Spfa(i, k); // 判负环
}
return o;
}
double Search() { // 二分
double l = 0, r = 1e9;
for (double mid; l + eps < r;) {
mid = (l + r) / 2.0;
if (Check(mid)) {
l = mid;
} else {
r = mid;
}
}
return l;
}
int main() {
cin >> n >> m;
n += 2;
for (int i = 1, x, y, a, b, c, d; i <= m; i++) {
cin >> x >> y >> a >> b >> c >> d;
e[x].push_back({y, (double)(b + d)}); // 建边
if (c > 0) e[y].push_back({x, (double(a - d))});
}
cout << fixed << setprecision(2) << Search();
return 0;
}
完结撒花 \ / \ / \ /
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效