首页 > 其他分享 >01分数规划

01分数规划

时间:2023-07-17 22:44:36浏览次数:39  
标签:分数 01 int double sum mid kmax include 规划

01分数规划属于二分法的一个应用,主要用于解决有关 “最优比率” 的问题,如最优比率背包、最优比率生成树等。

题目大致是说,给定两个长度均为 \(n\) 的数组 \(a、b\),要从中选出 \(k\) 组 \(a\) 和 \(b\),求 \(max\dfrac{\sum_{i=1}^na_is_i}{\sum_{i=1}^nb_is_i}\)。其中 \(s_i =0\)(不选) 或 \(s_i=1\)(选),且 \(\sum_{i=1}^ns_i=k\)。

考虑如何求解。

最暴力的方法就是二进制枚举,复杂度 \(O(2^n)\),时间太劣。

我们考虑去猜数,猜测所求的值 \(c\),使得:

\[\dfrac{\sum_{i=1}^na_is_i}{\sum_{i=1}^nb_is_i} \geq c \]

移项可得:

\[\sum_{i=1}^na_is_i \geq c \times \sum_{i=1}^nb_is_i \]

\[\sum_{i=1}^na_is_i \geq \sum_{i=1}^n(c\times b_is_i) \]

\[\sum_{i=1}^n(a_is_i-c\times b_is_i) \geq 0 \]

\[\sum_{i=1}^ns_i(a_i-c\times b_i) \geq 0 \]

此时我们要对每一个 \(i\) 求出 \(a_i - c\times b_i\),记为 \(d_i\)。那么显然,取 \(d_i\) 最大的 \(k\) 个元素最优。

此外,当 \(c\) 单调递增时,\(d_i\) 是单调递减的。于是我们可以通过二分提高猜数的效率,大幅缩短程序运行时间。

总时间复杂度 \(O(n\log n)\)

P4377 [USACO18OPEN] Talent Show G

这道题将01分数规划与01背包结合,求 \(max\dfrac{\sum_{i=1}^nt_is_i}{\sum_{i=1}^nw_is_i}\),此外还有 \(\sum_{i=1}^nw_is_i\geq W\) 的限制。

因为 \(\sum_{i=1}^nw_is_i\) 可以大于 \(W\),所以在统计背包的时候,要将大于 \(W\) 的背包容量统一成 \(W\) 记录下来。

最后如果 \(f_W <0\),说明答案估大了,否则估小了。

代码:

#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分数规划下做树上背包。

记 \(f_{x,y}\) 表示在 \(x\) 的子树中一共选 \(y\) 个人最大的结果。

注意 \(0\) 号点要算进去,共选 \(m+1\) 人,\(dp\) 的终态是 \(f_{0, m+1}\)。

代码:

#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] 最小圈

简化题意:给定一张有向图,定义一个环的价值为环上边的边权平均值,求这张图所有环的价值最小值。

假设一个环有 \(k\) 条边,其权值为 \(\dfrac{\sum_{i=1}^kw_k}{k}\),我们猜测一个数 \(x\),使得:

\[\dfrac{\sum_{i=1}^kw_k}{k}\leq x \]

\[\sum_{i=1}^kw_k\leq kx \]

\[(\sum_{i=1}^kw_k)-kx\leq 0 \]

\[\sum_{i=1}^k(w_k-x)\leq 0 \]

也就是说,将原来每条边的边权减去 \(x\) 后,如果有向图中存在负环,不等式是合法的。

这里同样可以采用二分提高效率。

判负环时以每个点为起点,如果对同一点更新了多次答案,说明存在负环。

代码:

#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] 方伯伯运椰子

题目中的两种调整方式分别对应退流增广。最优方案是在最大流不变的情况下使压缩、扩容总费用和最少。

更形式化点说,存在残量网络使得:

  1. 扩容: \(u \rightarrow v\) 花费 \(b+d\)
  2. 压缩:(反向边有流量)满足 \(c>0\) 时 \(v \rightarrow u\) 花费 \(a-b\)

然后套用分数规划,同上题思路判负环即可。

为什么可行,需要借助一个定理 —— 消圈定理。

在某个流 \(f\) 中,若它对应的残余网络没有负环,那么它一定就是当前流量下的最小费用流。

代码:

#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;
}

完结撒花 \ / \ / \ /

标签:分数,01,int,double,sum,mid,kmax,include,规划
From: https://www.cnblogs.com/ereoth/p/17561499.html

相关文章

  • 题解 P4815 [CCO2014] 狼人游戏
    看题目限制,可以发现如果将机器人作为点,指控和保护关系作为边,可以建出一个森林,就下来就是传统的树形背包了。设\(f_{i,j,0/1}\)表示当前点为\(i\),子树内有\(j\)个狼人,当前点是否为狼人的方案数。初始化:\(f_{u,0,0}=f_{u,1,1}=1\)当前点为狼:指控:\(f_{u,j,1}=f_{u,j-k,......
  • 题解 P4322 [JSOI2016]最佳团体
    P4322[JSOI2016]最佳团体分数规划+树形背包。可以根据推荐关系建出一颗树,然后如果选了一点,则该点到根上的所有点都必须选。二分\(mid\),定义每个结点的权值,然后判断选\(k+1\)个节点的最大值是否大于\(0\)。设\(f_{i,j}\)为当前节点\(i\),在其子树内选了\(j\)个节点,最......
  • 题解 P7250 [BalticOI 2012 Day1] 山峰
    通过观察,可以发现此题和最小生成树十分相似(两个地点之间途经的最小值最大)。于是可以考虑这么做:通过bfs将每一个块预处理出来,并记录其编号、高度、类型(是否为高地)以及边缘的点。将每一个块按高度从大到小排序。依次枚举每个块:对于当前要处理的块,枚举其边界的所有点,......
  • VS2017配置OpenCV
    VS2017配置OpenCV0OpenCV介绍OpenCV(OpenSourceComputerVisionLibrary)是一个开源的计算机视觉库,它提供了丰富的图像处理和计算机视觉算法,可用于处理图像和视频数据。OpenCV提供了C语言版本,使开发者可以使用C语言来调用OpenCV提供的功能。OpenCV可以用来进行多种图像处理......
  • Stable-Diffusion-webUI 代码阅读01 —— 从启动开始
    Stable-Diffusion-webUI代码阅读01——从启动开始由于实习工作需要,决定用几天时间阅读一遍stable-diffusion-webui的代码。本文参考知乎专栏,并且做出了一定程度上的改进,感谢大佬!知乎专栏:自动做游戏:AI技术落地于游戏开发-知乎(zhihu.com)最近工作主要侧重于OneFlow框架应......
  • 【2023.07.17】keeppley周杰伦DZ0157周同学积木评测
    前言本人是自费购买积木,购买原因是给妹妹培养动手能力,减少短视频占用时间,其次是给家里做摆饰,所以选择积木多考虑了美观非专业评测,如果想看更多积木评测请点进我的博客主页分类查看正文这次的说明书颜色真的印刷质量感觉不太好(单指颜色,拼装过程说明还是很不错的),颜色真的很杂......
  • P7809 [JRKSJ R2] 01 序列 题解
    前言传送门blog思路Problem1问题一问的是最长不下降子序列的长度,在一个$01$串中的最长不下降子序列,总共有三种$000\dots$,$000\dots111\dots$和$111111\dots$。可以把找到以上三种最长不下降子序列问题变为:$$\max^r_{i=l}(\sum_{j=l}^i[a_j=0])+(\sum_{j=i+1}^......
  • python连接Mysql 1-01
    一,下载对应python环境的MySQL连接包我的是python3所以下载的是这个(cmd)pip3installPyMySQL二,创建py文件编写importpymysql#打开数据库连接db=pymysql.connect(host='localhost',user='root',password='123456',db='test1')#使用cursor()方法创建一个游......
  • SQL Server 2016 KB2919355 安装失败
    WindowsServer2012R2安装SQLServer2016检查未通过,需要安装KB2919355。错误如下图: 按提示,下载安装WindowsServer2012R2更新(KB2919355),下载文件为:Windows8.1-KB2919355-x64.msu(690MB)。但是安装时又提示错误! KB2919442是WindowsServer2012R2更新......
  • 跟运维学 Linux - 01
    跟运维学Linux-01运维的诞生运维工程师有很多叫法:系统运维、Linux工程师、系统管理员...网管可以说是运维工程师最早的雏形。在个人电脑未普及时,大家去网吧玩游戏。玩家:“网关,我的电脑上不了网了”网管负责维修电脑、维修系统、维护网络设备...互联网的发展现在大家在......