算法:分数规划学习笔记

算法简介

分数规划是一个与二分紧密结合的算法技巧,关于它的习题通常可以得到如下式子:

\[\frac {\sum p_i \times w_i} {\sum q_i \times w_i} \ge {mid} \]

然后通过变化得出:

\[({\sum p_i \times w_i} - {mid}\times{ q_i \times w_i}) \ge 0 \]

接着再根据实际情况建构对应模型解决问题。

例题讲解

P10505 Dropping Test

分数规划模板题,直接按照上面的的方式推导一下,然后令 \(c_i = a_i - mid \times b_i\),取最大的 \(n-k\)\(c_i\) 求和检查即可。

#include <bits/stdc++.h>
using namespace std; 
const double eps = 1e-6; 
const int N = 1000 + 5; 
int n, k, a[N], b[N];
double c[N];  
bool chk(double mid) {
    for(int i = 1; i <= n; i++) c[i] = a[i] - mid * b[i]; 
    sort(c + 1, c + n + 1, greater<double>());  
    double ret = 0; 
    for(int i = 1; i <= n - k; i++) ret += c[i]; 
    return ret >= 0; 
}
int main() {
while(true) {
    scanf("%d %d", &n, &k); 
    if(!n && !k) return 0; 
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]); 
    for(int i = 1; i <= n; i++) scanf("%d", &b[i]); 
    double l = 0, r = 1e12; 
    while(r - l > eps) {
        double mid = (l + r) / 2.0; 
        if(chk(mid)) l = mid; 
        else r = mid; 
    } 
    printf("%.0lf\n", l * 100); 
}
}

P4322 [JSOI2016] 最佳团体

分数规划结合背包 DP。

P2868 [USACO07DEC] Sightseeing Cows G

这是最优比率环模型的应用。

题意转化一下,就可以变成二分答案 \(mid\),检查是否有

\[\frac {\sum {F_i}}{\sum{E_i}} > mid \]

其中 \(\sum E_i\) 表示每一条经过的边。

转化一下就有:

\[\sum({F_i}-mid \times {E_i}) > 0 \]

这样就可以直接边跑最长路边判是否有正环,但我实现时把式子取反跑判负环,两种写法均可通过。

#include <cstdio> 
#include <queue> 
using namespace std; 
typedef long long i64; 
const int N = 1000 + 5; 
const int M = 5000 + 5; 
const double eps = 1e-6; 
int n, m, fun[N], hd[N], cnte, cnt[N];
double dis[N];   
bool vis[N]; 
struct Edges {
    int nxt, to, val; 
} ed[M]; 
queue<int> q; 
void addedge(int u, int v, int w) {
    ed[++cnte] = (Edges) {hd[u], v, w}, hd[u] = cnte; 
    return;  
}  
bool chk(double mid) {
    for(int i = 1; i <= n; i++) {
        q.push(i); 
        dis[i] = 0; 
        vis[i] = 1; 
        cnt[i] = 1; 
    } 
    while(!q.empty()) {
        int u = q.front(); q.pop(); 
        vis[u] = 0; 
        for(int i = hd[u]; i; i = ed[i].nxt) {
            int v = ed[i].to; 
            int w = ed[i].val; 
            if(dis[v] > dis[u] + mid * w - fun[u]) {
                dis[v] = dis[u] + mid * w - fun[u];  
                if(!vis[v]) {
                    q.push(v); 
                    vis[v] = 1; 
                    cnt[v]++; 
                    if(cnt[v] >= n) return true;  
                }
            } 
        }
    } 
    return false; 
}
int main() {
    scanf("%d %d", &n, &m); 
    for(int i = 1; i <= n; i++) scanf("%d", &fun[i]); 
    for(int u, v, w, i = 1; i <= m; i++) {
        scanf("%d %d %d", &u, &v, &w); 
        addedge(u, v, w); 
    } 
    double l = 0, r = 1e6; 
    while(r - l > eps) {
        double mid = (l + r) / 2.0; 
        if(chk(mid)) l = mid; 
        else r = mid; 
    } 
    printf("%.2lf", l);
    return 0; 
}

注意,本题需要在 \(T_i\ge1\) 的情况下才能保证重复经过一个点不优,从而使用该算法解决。

posted @ 2026-03-11 20:02  Tiger_Rory  阅读(1)  评论(0)    收藏  举报
//雪花飘落效果