OSDN Git Service

update fractional-programming.md
authorMout-sea <2582621015@qq.com>
Sun, 4 Aug 2019 04:18:46 +0000 (12:18 +0800)
committerMout-sea <2582621015@qq.com>
Sun, 4 Aug 2019 04:18:46 +0000 (12:18 +0800)
docs/misc/fractional-programming.md

index 29e26b4..fd8356b 100644 (file)
-!!! note " 例题[luogu P4322\[JSOI2016\]最佳团体](https://www.luogu.org/problemnew/show/P4322)"
-    题目大意:有一棵 $n+1$ 个结点的树,根为 $0$ 号结点。每个结点 $i$ 有一个价值 $p_i$ 和费用 $s_i$ 。你需要选择 $k$ 个结点 $a_1,a_2,\ldots,a_k$ (不包括 $0$ 号结点),使得
+分数规划用来求一个分式的极值。
 
-    $$
-    \frac{\sum_{i=1}^k p_{a_i}}{\sum_{i=1}^k s_{a_i}}
-    $$
+形象一点就是,给出 $a_i$ 和 $b_i$ ,求一组 $w_i\in[0,1]$,最小化或最大化
+$$
+\displaystyle\frac{\sum\limits_{i=1}^na_i\times w_i}{\sum\limits_{i=1}^nb_i\times w_i}
+$$
 
-    最大。你需要保证对于你选择的一个树上结点,它的父亲一定被选中。求出这个最大的比值。
+另外一种描述:每种物品有两个权值 $a$ 和 $b$ ,选出若干个物品使得 $\displaystyle\frac{\sum a}{\sum b}$ 最小/最大。 
 
-如果每个点都只有一个价值(设为 $v_i$ ),我们要做的只是最大化这个最后能得到的价值,那么我们可以用树形动态规划解决这个问题。
+## 求解
 
®\9aä¹\89 $f(i,j)$ è¡¨ç¤ºä»¥ $i$ ä¸ºæ ¹ç\9a\84å­\90æ \91中é\80\89æ\8b© $j$ ä¸ªç»\93ç\82¹ç\9a\84æ\9c\80大价å\80¼ã\80\82ç\94±äº\8eè¦\81满足å¦\82æ\9e\9cä¸\80个ç\82¹è¢«é\80\89æ\8b©ï¼\8cå\85¶ç\88¶äº²ä¹\9fä¸\80å®\9a被é\80\89æ\8b©ï¼\8cå¦\82æ\9e\9c $j\ge 1$ ï¼\8cé\82£ä¹\88å½\93å\89\8dç»\93ç\82¹ $i$ ä¸\80å®\9aæ\98¯å·²ç»\8f被é\80\89中ç\9a\84ã\80\82å\88©ç\94¨æ \91ä¸\8aè\83\8cå\8c\85è¿\9bè¡\8cå\90\88并ã\80\82å\86\99å\87ºç\8a¶æ\80\81转移æ\96¹ç¨\8bï¼\9a
\88\86æ\95°è§\84å\88\92é\97®é¢\98ç\9a\84é\80\9aç\94¨æ\96¹æ³\95æ\98¯äº\8cå\88\86ã\80\82
 
+假设我们要求最大值。二分一个答案$mid$,然后推式子(为了方便少写了上下界):
 $$
-f(i,j)=\left\{
+\displaystyle
 \begin{aligned}
-& 0, & j=0, \\
-& \max\left\{\sum f(son,k)\right\}+v(i),\quad\text{where }\sum k=j-1, & j\neq 0
-\end{aligned} \right
-.
+&\frac{\sum a_i\times w_i}{\sum b_i\times w_i}>mid\\
+\Longrightarrow&\sum a_i\times w_i-mid\times \sum b_i\cdot w_i>0\\
+\Longrightarrow&\sum w_i\times(a_i-mid\times b_i)>0
+\end{aligned}
 $$
+那么只要求出不等号左边的式子的最大值就行了。如果最大值比 $0$ 要大,说明 $mid$ 是可行的,否则不可行。
 
-如果 **合并方式得当** ,则可以在 $O(n^2)$ 的时间复杂度内完成状态转移,具体细节参见代码
+求最小值的方法和求最大值的方法类似,读者不妨尝试着自己推一下
 
-但是,我们是让一个分式的值最大化,然而这个分式的分母又不确定,怎么办呢?
+----
 
-首先我们明确一个性质:设最后最大化的答案为 $ans$ ,那么对于任意一个小于等于 $ans$ 的实数 $a$ ,都有 $v_i=p_i-a\times s_i$ ,这样的 $v$ 数组算出的 $f(0,k+1)$ 的值都大于零。该式等于零当且仅当 $a=ans$ 
+分数规划的主要难点就在于如何求 $\displaystyle \sum w_i\times(a_i-mid\times b_i)$ 的最大值/最小值。下面通过一系列实例来讲解该式子的最大值/最小值的求法
 
-那么,我们就可以用二分的思想解决这个问题。每次选择一个 $mid$ ,令 $v_i=p_i-mid\times s_i$ ,算出 $f(0,k+1)$ 的值,如果大于零则选择右区间,反之选择左区间。最后当区间 $[l,r]$ 的大小 $r-l$ 在可以容许的范围内时,输出最后的答案。时间复杂度为 $O(n^2\log ans)$ 。
+## 实例
 
-这就是分数规划的基本思想。
+### POJ2976 Dropping tests
 
-代码:
+> 没有限制的分数规划。
+
+如果 $a_i-mid\times b_i>0$ ,那么把 $w_i$ 设成 $1$ ,否则把 $w_i$ 设成 $0$ ,即可求出最大值。
 
 ```cpp
-// luogu-judger-enable-o2
-#include <algorithm>
-#include <cstdio>
-#include <cstring>
-using namespace std;
-const int maxn = 2510;
-int k, n, rr, cur, h[maxn], nxt[maxn * 2], p[maxn * 2], siz[maxn];
-double s[maxn], pp[maxn], l, r, mid, v[maxn], f[maxn][maxn];
-void add_edge(int x, int y) {
-  cur++;
-  nxt[cur] = h[x];
-  h[x] = cur;
-  p[cur] = y;
+bool check(double mid)
+{
+    double sum = 0;
+    for (int i = 1; i <= n; ++ i)
+        if (a[i] - mid * b[i] > 0)
+            sum += a[i] - mid * b[i];
+    return sum > 0;
 }
-void dfs(int x, int fa) {
-  siz[x] = 1;
-  f[x][0] = 0;
-  f[x][1] = v[x];
-  for (int i = h[x]; i != -1; i = nxt[i])
-    if (p[i] != fa) {
-      dfs(p[i], x);
-      for (int j = siz[x]; j >= 1; j--)
-        for (int k = 0; k <= siz[p[i]]; k++)
-          f[x][j + k] = max(f[x][j + k], f[x][j] + f[p[i]][k]);
-      siz[x] += siz[p[i]];
+
+```
+
+### 洛谷4377 Talent Show
+
+> 要求 $\displaystyle\sum w_i\times b_i \geq W​$ 的分数规划。
+
+本题多了分母至少为 $W​$ 的限制,因此无法再使用上一题的贪心算法。
+
+可以考虑 01 背包。把 $b_i$ 作为第 $i$ 个物品的重量,$a_i-mid\times b_i$ 作为第 $i$ 个物品的价值,然后问题就转化为背包了。
+
+那么 $dp[n][W]$ 就是最大值。
+
+一个要注意的地方:$\sum w_i\times b_i​$ 可能超过 $W​$ ,此时直接视为 $W​$ 即可。(想一想,为什么?)
+
+```cpp
+double f[1010];
+inline bool check(double mid)
+{
+    for (int i = 1; i <= W; i ++) f[i] = -1e9;
+    for (int i = 1; i <= n; i ++)
+        for (int j = W; j >= 0; j --)
+        {
+            int k = min(W, j + b[i]);
+            f[k] = max(f[k], f[j] + a[i] - mid * b[i]);
+        }
+    return f[W] > 0;
+}
+```
+
+### POJ2728 Desert King
+
+> 每条边有两个权值 $a_i$ 和 $b_i$ ,求一棵生成树 $T$ 使得 $\displaystyle\frac{\sum_{e\in T}a_e}{\sum_{e\in T}b_e}$ 最小。
+
+把 $a_i-mid\times b_i$ 作为每条边的权值,那么最小生成树就是最小值,
+
+代码就是求最小生成树,我就不放代码了。
+
+### [HNOI2009]最小圈
+
+> 每条边的边权为 $w$ ,求一个环 $C$ 使得 $\displaystyle\frac{\sum_{e\in C}w}{|C|}$ 最小。
+
+把 $a_i-mid$ 作为边权,那么权值最小的环就是最小值。
+
+因为我们只需要判最小值是否小于 $0$ ,所以只需要判断图中是否存在负环即可。
+
+另外本题存在一种复杂度 $O(nm)$ 的算法,如果有兴趣可以阅读[这篇文章](https://www.cnblogs.com/y-clever/p/7043553.html)。
+
+```cpp
+inline int SPFA(int u,double mid) //判负环
+{
+    vis[u]=1;
+    for (int i = head[u]; i; i = e[i].nxt)
+    {
+        int v = e[i].v;
+        double w = e[i].w - mid;
+        if (dis[u] + w < dis[v])
+        {
+            dis[v] = dis[u] + w;
+            if (vis[v] || SPFA(v,mid)) return 1;
+        }
     }
+    vis[u] = 0;
+    return 0;
 }
-int main() {
-  memset(h, -1, sizeof h);
-  scanf("%d%d", &k, &n);
-  for (int i = 1; i <= n; i++)
-    scanf("%lf%lf%d", s + i, pp + i, &rr), add_edge(rr, i), add_edge(i, rr);
-  r = 10000.0;
-  while (r - l > 1e-4) {
-    mid = (l + r) * 0.5;
-    for (int i = 1; i <= n; i++) v[i] = pp[i] - mid * s[i];
-    for (int i = 0; i < maxn; i++)
-      for (int j = 0; j < maxn; j++) f[i][j] = -2e9;
-    dfs(0, -1);
-    if (f[0][k + 1] > 0)
-      l = mid;
-    else
-      r = mid;
-  }
-  printf("%.3lf\n", l);
-  return 0;
+
+inline bool check(double mid) //如果有负环返回 true
+{
+    for (int i = 1; i <= n; ++ i) dis[i] = 0, vis[i] = 0;
+    for (int i = 1; i <= n; ++ i)
+        if (SPFA(i,mid)) return 1;
+    return 0;
 }
 ```
 
-## 习题
+## 总结
+
+分数规划问题是一类既套路又灵活的题目,一般使用二分解决。
 
-[luogu P3705\[SDOI2017\]新生舞会](https://www.luogu.org/problemnew/show/P3705)
+分数规划问题的主要难点在于推出式子后想办法求出 $\displaystyle\sum w_i\times(a_i-mid\times b_i)$ 的最大值/最小值,而这个需要具体情况具体分析。
 
-[luogu P3288\[SCOI2014\]方伯伯运椰子](https://www.luogu.org/problemnew/show/P3288)
+## 习题
 
-[luogu P3199\[HNOI2009\]最小圈](https://www.luogu.org/problemnew/show/P3199)
+- [JSOI2016 最佳团体](https://www.luogu.org/problem/P4322)
+- [SDOI2017 新生舞会](https://www.luogu.org/problem/P3705)
+- [UVa1389 Hard Life](https://www.luogu.org/problem/UVA1389)
\ No newline at end of file