*【学习笔记】(16) 0/1 分数规划
1.算法介绍
分数规划用来求一个分式的极值。
比如说有
2.求解
2.1 二分法
假设我们要求上式的最大值。
再加一个限制 :
那么令
那么必然有:
化简得:
带入得:
这样就把整体的贡献转化为了单件物品的贡献,则只需要二分这个
2.2 Dinkelbach 算法
二分法能通过大部分的题目,但效率有时太低了。
对上面的式子进行处理:
其中
构造一个函数:
其中
图片引用自 https://www.cnblogs.com/KirisameMarisa/p/4187637.html
我们需要找到最大的
我们在判断一个当前的
3.例题
Ⅰ. 分数规划裸题 POJ2976 Dropping tests
板题。
点击查看代码
#include<cstdio> #include<algorithm> #define db double #define eps 1e-6 using namespace std; const int N = 1e3 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, k; db a[N], b[N], c[N]; bool check(db x){ for(int i = 1; i <= n; ++i) c[i] = a[i] - b[i] * x; sort(c + 1, c + 1 + n); db sum = 0; for(int i = k + 1; i <= n; ++i) sum += c[i]; return sum >= 0; } signed main(){ while(1){ n = read(), k = read(); if(!n && !k) exit(0); for(int i = 1; i <= n; ++i) a[i] = read(); for(int i = 1; i <= n; ++i) b[i] = read(); db l = 0, r = 1e6, ans = 0; while(r - l > eps){ db mid = (l + r) / 2.0; if(check(mid)) ans = mid, l = mid; else r = mid; } printf("%.f\n", 100 * ans); } return 0; }
Ⅱ.最优比率背包 P4377 [USACO18OPEN] Talent Show G
题目大意与上题类似,加了一个限制,要求
那二分还是和之前一样,但是判断方式要发生改变,之前是贪心的取前
点击查看代码
#include<cstdio> #include<algorithm> #include<cstring> #define inf -0x3f3f3f3f #define int long long using namespace std; const int N = 1e4 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, V; int w[N], t[N], f[N]; bool check(int x){ memset(f, -0x3f, sizeof(f)); f[0] = 0; for(int i = 1; i <= n; ++i) for(int j = V; ~j; --j) if(f[j] != inf) f[min(V, j + w[i])] = max(f[min(V, j + w[i])], f[j] + t[i] - x * w[i]); return f[V] >= 0; } signed main(){ // freopen("1.in", "r", stdin); n = read(), V = read(); for(int i = 1; i <= n; ++i) w[i] = read(), t[i] = read() * 1000; int l = 0, r = 1e6, ans = 0; while(l <= r){ int mid = (l + r) >> 1; if(check(mid)) ans = mid, l = mid + 1; else r = mid - 1; } printf("%lld\n", ans); return 0; }
Ⅲ.最优比率生成树 POJ 2728 Desert King
对于构造最优比率生成树的分数规划,其实差不多类似是将
那么求法呢,其实也很类似:
就是令边权为
(这道题好 sb 啊,为什么 %.3lf 和 $.3f 结果就不一样了呢?)
点击查看代码
#include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #define ll long long #define db double #define eps 1e-5 using namespace std; const int N = 2e3 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n; bool vis[N]; db f[N][N], g[N][N], minn[N]; db sum, tmp; struct village{ db x, y, z; }a[N]; db Dis(village a, village b){ return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); } bool prim(db r){ memset(vis, 0, sizeof(vis)); sum = 0, vis[1] = 1; for(int i = 1; i <= n; ++i) minn[i] = g[1][i] - f[1][i] * r; for(int i = 2, k, j; i <= n; ++i){ for(tmp = 1e18, k = i - 1, j = 2; j <= n; ++j) if(!vis[j] && minn[j] < tmp) tmp = minn[j], k = j; for(vis[k] = 1, sum += tmp, j = 2; j <= n; ++j) if(!vis[j]) minn[j] = min(minn[j], g[k][j] - f[k][j] * r); } return sum - eps <= 0; } signed main(){ while(1){ n = read(); if(!n) break; for(int i = 1; i <= n; ++i) a[i].x = read(), a[i].y = read(), a[i].z = read(); for(int i = 1; i <= n; ++i) for(int j = i + 1; j <= n; ++j){ f[i][j] = f[j][i] = Dis(a[i], a[j]); g[i][j] = g[j][i] = fabs(a[i].z - a[j].z); } db l = 0, r = 1e3, ans = 0; while(r - l > eps){ db mid = (l + r) / 2.0; if(prim(mid)) ans = mid, r = mid; else l = mid; } printf("%.3f\n", ans); } return 0; }
Ⅳ.最优比率环 P3199 [HNOI2009]最小圈
简而言之,最优比率环就是给你一个图,图上每条边有两个权值(当然,第二个权值可能恒为 1 ),然后让你在图中找到一个环,令环上边的个两个权值和的比值最大(或是最小)
然后就是要用上面生成树类似的思路,得到每条边边权为:
远古马蜂
点击查看代码
#include<bits/stdc++.h> #define N 3005 #define M 10005 #define db double using namespace std; int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } int n,m,tot,flag; int Head[N],to[M],Next[M],vis[N]; db edge[M],d[N],ans; void add(int u,int v,db w){to[++tot]=v,Next[tot]=Head[u],Head[u]=tot,edge[tot]=w;} void dfs(int x,db xx){ vis[x]=1; for(int i=Head[x];i;i=Next[i]){ int y=to[i]; if(d[y]>d[x]+edge[i]-xx){ if(vis[y]||flag){flag=1;break;} d[y]=d[x]+edge[i]-xx; dfs(y,xx); } } vis[x]=0; } int main(){ n=read(),m=read(); for(int i=1;i<=m;++i){ int u=read(),v=read();db w;scanf("%lf",&w),add(u,v,w); } db l=-1e7,r=1e7; while(r-l>1e-10){ db mid=(l+r)/2;flag=0; memset(vis,0,sizeof(vis)); for(int i=1;i<=n;i++) d[i]=0; for(int i=1;i<=n;i++){ dfs(i,mid);if(flag) break; } if(flag) ans=mid,r=mid; else l=mid; } printf("%.8lf\n",ans); return 0; }
Ⅴ.最大密度子图 UVA1389 Hard Life
update on 2023.6.2 等我复习完网络流再回来
Ⅵ.最优比率流 P3705 [SDOI2017] 新生舞会
类似 方格取数 和 0/1 分数规划的结合体。
所以 二分 套上一个 网络流 即可。
这里使用费用流, 每行向 源点 连边,边权 1, 费用 0, 每列向 汇点 连边,边权 1, 费用
double 类型居然不能 memset, 长见识了。
点击查看代码
#include<bits/stdc++.h> #define db double #define eps 1e-7 #define INF 0x3f3f3f3f using namespace std; const int N = 1e3 + 67, M = 2e5 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, s, t; bool vis[N]; db res = 0, val[M << 1], a[N][N], b[N][N], dis[N]; int tot, Head[N], to[M << 1], Next[M << 1], edge[M << 1], now[N]; void add(int u, int v, int w, db vv){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot, edge[tot] = w, val[tot] = vv; to[++tot] = u, Next[tot] = Head[v], Head[v] = tot, edge[tot] = 0, val[tot] = -vv; } bool spfa(){ for(int i = 1; i <= t; ++i) vis[i] = 0, dis[i] = -INF; queue<int> q; dis[s] = 0, q.push(s), now[s] = Head[s], vis[s] = 1; while(!q.empty()){ int x = q.front(); q.pop(), vis[x] = 0; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(!edge[i]) continue; if(dis[y] < dis[x] + val[i]){ now[y] = Head[y]; dis[y] = dis[x] + val[i]; if(!vis[y]) q.push(y), vis[y] = 1; } } } return dis[t] != -INF; } int dinic(int x, int flow){ vis[x] = 1; if(x == t) return flow; int rest = flow; for(int i = now[x]; i && rest; i = Next[i]){ int y = to[i]; now[x] = i; if((vis[y] && y != t) || !edge[i] || dis[y] != dis[x] + val[i]) continue; int k = dinic(y, min(rest, edge[i])); if(k) res += val[i] * k, edge[i] -= k, edge[i ^ 1] += k, rest -= k; } return flow - rest; } bool check(db r){ res = 0; memset(Head, 0, sizeof(Head)), tot = 1; for(int i = 1; i <= n; ++i) add(s, i, 1, 0), add(i + n, t, 1, 0); for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j) add(i, j + n, 1, a[i][j] - b[i][j] * r); int flow = 0; while(spfa()){ vis[t] = 1; while(vis[t]){ memset(vis, 0, sizeof(vis)); flow += dinic(s, INF); } } return res >= 0; } int main(){ n = read(); s = n * 2 + 1, t = n * 2 + 2; for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j) a[i][j] = read(); for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j) b[i][j] = read(); db l = 0, r = 1e4, ans = 0; while(r - l > eps){ db mid = (l + r) / 2.0; if(check(mid)) l = mid, ans = mid; else r = mid; } printf("%.6lf\n", ans); return 0; }
参考:
https://www.cnblogs.com/KirisameMarisa/p/4187637.html
https://www.cnblogs.com/Judge/p/10173795.html
本文作者:南风未起
本文链接:https://www.cnblogs.com/jiangchen4122/p/17438441.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步