📚【模板】分数规划
给定\(n\)个物品和每个物品的属性\(a_i,b_i\)。
请求出:
\[\max\left(\frac{\sum\limits_{i=1}^{n}a_i\times x_i}{\sum\limits_{i=1}^{n}b_i\times x_i}\right)
\]
其中\(x_i\in \left\{0,1\right\}\),并且恰有\(k\)个\(x_i=1\)。
实际上需要二分。
判断一个结果\(mid\)是否可行:
\[\begin{aligned}
&\frac{\sum\limits_{i=1}^{n}a_i\times x_i}{\sum\limits_{i=1}^{n}b_i\times x_i}>mid\\
\implies&\sum\limits_{i=1}^{n}x_i\times \left(a_i-mid\times b_i\right) > 0
\end{aligned}\]
#include <stdio.h>
#include <bits/stl_algobase.h>
#include <bits/stl_algo.h>
const int N = 131026;
const double eps = 1e-8;
int n, k;
int a[N], b[N];
double c[N];
bool check(double now) {
double res = 0;
for(int i = 1;i <= n;++i)
c[i] = a[i]-now*b[i];
std :: sort(c+1,c+n+1);
for(int i = 1;i <= k;++i)
res += c[n-i+1];
return (res > 0);
}
signed main() {
scanf("%d %d",&n,&k);
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 = 998244353;
while(r-l > eps) {
double mid = (l+r)/2.0;
if(check(mid)) l = mid;
else r = mid;
}
printf("%.4lf",l);
}
\(\text{luogu P4322 [JSOI2016]最佳团体}\)
#include <stdio.h>
#include <bits/stl_algobase.h>
#include <string.h>
const int N = 2048+512;
const double eps = 1e-8;
struct EDGE {
int t, next;
} edge[N<<1];
int edge_tot, head[N];
void add_edge(int f,int t) {
edge[++edge_tot].next = head[f];
edge[edge_tot].t = t;
head[f] = edge_tot;
}
int n, k;
int s[N], p[N];
int size[N];
double dp[N][N];
double val[N];
void predfs(int u) {
size[u] = 1;
for(int i = head[u];i;i = edge[i].next) {
int v = edge[i].t;
predfs(v);
size[u] += size[v];
}
}
void dfs(int u) {
dp[u][1] = val[u];
for(int i = head[u];i;i = edge[i].next) {
int v = edge[i].t;
dfs(v);
for(int j = std :: min(size[u],k+1);j;--j)
for(int l = 0;l <= std :: min(size[v],j-1);++l)
dp[u][j] = std :: max(dp[u][j],dp[u][j-l]+dp[v][l]);
}
}
bool check(double now) {
for(int i = 1;i <= n;++i)
val[i] = p[i]-now*s[i];
for(int i = 0;i <= n;++i)
for(int j = 1;j <= k+1;++j)
dp[i][j] = -98244353;
dfs(0);
return dp[0][k+1] >= 0;
}
signed main() {
scanf("%d %d",&k,&n);
for(int i = 1, f;i <= n;++i) {
scanf("%d %d %d",&s[i],&p[i],&f);
add_edge(f,i);
}
predfs(0);
double l = 0, r = 65536;
while(r-l > eps) {
double mid = (l+r)/2.0;
if(check(mid)) l = mid;
else r = mid;
}
printf("%.3lf",l);
}
一个分数规划套树背包
套路跟题号一样老经典
对于价值 \(a_i\) 和污染 \(b_i\),我们要求一个以 \(u\) 为根的大小为 \(n-m\) 的联通块的 \(\sum a_i-mid\times b_i\)。
在树上 DP
即可。
#include <iostream>
#include <cstring>
const int N = 101;
const double eps = 1e-6;
const double inf = 1e9;
struct EDGE {
int t, next;
} edge[N<<1];
int edge_tot, head[N];
void add_edge(int f,int t) {
edge[++edge_tot].next = head[f];
edge[edge_tot].t = t;
head[f] = edge_tot;
}
int n, m;
int a[N], b[N];
double dp[N][N], val[N];
int size[N];
void dfs(int u,int p) {
size[u] = 1;
for(int i = head[u], v;i;i = edge[i].next) {
v = edge[i].t;
if(v == p)
continue;
dfs(v,u);
size[u] += size[v];
for(int j = std :: min(m,size[u]);j >= 0;--j)
for(int k = 0;k <= std :: min(j,size[v]);++k)
dp[u][j] = std :: max(dp[u][j],dp[u][j-k]+dp[v][k]);
}
for(int i = std :: min(m,size[u]);i;--i)
dp[u][i] = dp[u][i-1]+val[u];
}
bool check(double x) {
for(int i = 1;i <= n;++i)
for(int j = 1;j <= m;++j)
dp[i][j] = -inf;
for(int i = 1;i <= n;++i)
val[i] = 1.0*a[i]-x*b[i];
dfs(1,0);
for(int i = 1;i <= n;++i)
if(dp[i][m] > -eps)
return true;
return false;
}
int main() {
scanf("%d %d",&n,&m);
m = n-m;
for(int i = 1;i <= n;++i)
scanf("%d",&a[i]);
for(int i = 1;i <= n;++i)
scanf("%d",&b[i]);
for(int i = 1, u, v;i < n;++i) {
scanf("%d %d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
double l = 0, r = 1e6;
while(r-l > eps) {
double mid = (l+r)/2.0;
if(check(mid))
l = mid;
else
r = mid;
}
printf("%.1lf\n",l);
return 0;
}