01 分数规划
P2868 Sightseeing Cows G
给出一个 \(n\) 个点 \(m\) 条的有点权与边权的有向图,求一个环使得环上的点权和除以边权和最大。
最优比率环问题,显然01分数规划
环上第\(i\)个点点权为\(a_i\),第\(i\)条边的边权为\(b_i\)
\[\frac{\sum_{i=1}^nF_i}{\sum_{i=1}^mT_i}=ans\\
对于01分数规划,考虑二分.二分可以将最优化问题转化为判定问题.如果二分出来mid为L,则问题转化为是否存在\\
\frac{\sum F_i}{\sum T_i}>L~~~分母乘过去(T_i>0)\\
\sum(F_i-L*T_i)>0~~左式乘-1~~~\sum(L*T_i-F_i)<0\\
问题转化为求负环,对于入点为u_i,出点为v_i的边e_i,把边权看做mid*T[e_i]-F[u_i],判负环\\
有负环则L=mid,否则R=mid
\]
#include<cstdio>
#include<queue>
#include<iostream>
#define maxm 5005
#define maxn 1002
using namespace std;
inline int read() {
int x = 0,f = 1; char s = getchar();
while(s < '0' || s > '9') {if(s == '-') f = -1;s = getchar();}
while(s >= '0' && s <= '9') {x = x * 10 + s - '0';s = getchar();}
return f * x;
}
int l,p,head[maxn],tot = 0,vis[maxn],num[maxn],a[maxn];
double d[maxn];
struct edge{
int to,next,dis;
}e[maxm];
inline void add(int u,int v,int w){
e[++tot].to = v;
e[tot].next = head[u];
e[tot].dis = w;
head[u] = tot;
}
int check(double x){//找负环
queue<int> q;
for(int i = 1;i <= l;i++){
q.push(i);
d[i] = 0;vis[i] = num[i] = 1;
}
while(!q.empty()){
int u = q.front();
q.pop();vis[u] = 0;
for(int i = head[u];i;i = e[i].next){
int v = e[i].to;
double dis = (double)e[i].dis;
if(d[v] > d[u] + x * dis - (double)a[u])//边权为mid*Tim[e_i]-Fun[u_i]
{
d[v] = d[u] + x * dis - (double)a[u];
if(!vis[v])
{
q.push(v); vis[v]=1;
if(++num[v] >= l) return 1;
}
}
}
}
return 0;
}
int main(){
l=read();p=read();
for(int i=1;i<=l;++i)a[i]=read();
for(int i=1;i<=p;++i)
{
int u=read(),v=read(),dis=read();
add(u,v,dis);
}
double L=0,R=5001,mid;
while(R-L>1e-4)
{
mid=(L+R)/2;
if(check(mid))L=mid;
else R=mid;
}
printf("%.2lf",L);
return 0;
}
P4377 [USACO18OPEN]Talent Show G
乍眼一看,这题好裸,裸题就意味着神题(裸体就意味着身体)
\[1.分数规划\\
ans=\frac{\sum t_i}{\sum w_i}(\sum w_i\ge W)\\
\frac{\sum t_i}{\sum w_i}\ge mid \\
所以要求\sum (t_i-mid*w_i)\ge0,令c_i=t_i-mid*w_i\\
2.~~01背包,做个大小为W的背包,重量大于dp[w]的,都记在dp[w]上,最后只要看dp[W]是否>0
\]
#include<cstdio>
#include<iostream>
#define INF 0x3f3f3f3f
#define maxn 251
#define maxm 1005
using namespace std;
int n, W;
int w[maxn],t[maxn];
double dp[maxm],c[maxn];
inline int max(int a,int b){return a > b ? a : b;}
inline int check(double mid){
for(int i = 1;i <= n;i++) c[i] = (double)t[i] - mid * w[i];
for(int i = 1;i <= W;i++) dp[i] = -INF;
for(int i = 1;i <= n;i++)
for(int j = W;j >= 0;j--){
if(j + w[i] >= W) dp[W]=max(dp[W], dp[j] + c[i]);
else dp[j+w[i]]=max(dp[j+w[i]],dp[j]+c[i]);
}
return dp[W]>=0;
}
int main(){
scanf("%d%d",&n,&W);
double l=0,r=0;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&w[i],&t[i]);
r+=t[i];
}
while(l+1e-5<=r)
{
double mid=(l+r)/2;
if(check(mid))
{
l=mid;
}
else
r=mid;
}
int ans = (int)(l*1000);
printf("%d",ans);
return 0;
}