01分数规划
01 分数规划
此人在福州市 2025 国庆集训 Day2 T3 因为不会分数规划取得了 \(0\) 分的好成绩!
问题
给定两个长度为 \(n\) 的序列 \(a,b\)。需要从中选出 \(k\) 个下标 \(idx\),使得 \(\frac{\sum^k_{i=1}a_{idx_i}}{\sum^k_{i=1}b_{idx_i}}\) 最大或最小。
下文以最大为例子。
解法
我们考虑一个数字 \(x\),使 \(\frac{\sum^k_{i=1}a_{idx_i}}{\sum^k_{i=1}b_{idx_i}}\ge x\)。那么我们再来稍微推推式子,有:
\[\sum^k_{i=1}a_{idx_i}\ge x\cdot \sum^k_{i=1}b_{idx_i}\\
\sum^k_{i=1}a_{idx_i}-x\cdot \sum^k_{i=1}b_{idx_i}\ge 0\\
\sum^k_{i=1}(a_{idx_i}-x\cdot b_{idx_i})\ge 0\\
\]
所以我们可以把每个下标都给重新赋上权值,即 \(v_i=a_i-x\cdot b_i\),最后排个序。
因为是综合要大等于 \(0\),所以显然取前 \(k\) 个最大的是最优的。
那么如何确定 \(x\) 呢?
考虑直接二分。二分到一个 \(x\),代进去 check 一下,如果可以则更新左端点,不行则更新右端点。
因为如果一个 \(x\) 代进去发现小于 \(0\),即不合法,那么大于 \(x\) 的所有数更不可能合法。
综上,一般情况下的时间复杂度是 \(O(n\log^2 n)\)。
例题
模板题。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef double db;
const int N=1e5+5;
int n,k;
db c[N],t[N],a[N];
bool cmp(db a,db b){return a>b;}
bool check(db x)
{
for(int i=1;i<=n;++i)
a[i]=c[i]-x*t[i];
sort(a+1,a+n+1,cmp);
db ans=0.0;
for(int i=1;i<=k;++i)
ans+=a[i];
return ans<=0;
}
int main(){
ios::sync_with_stdio(0);
cin>>n>>k;
for(int i=1;i<=n;++i)cin>>c[i];
for(int i=1;i<=n;++i)cin>>t[i];
db l=0.0,r=1.0,mid;
while(l<r&&r-l>0.0001)
{
// cout<<l<<' '<<r<<' '<<mid<<'\n';
mid=(l+r)/2.0;
if(check(mid))r=mid;
else l=mid;
}
cout<<fixed<<setprecision(3)<<l<<'\n';
return 0;
}
也差不多是模板题,双倍经验了属于。
#include<bits/stdc++.h>
using namespace std;
typedef double db;
const int N=205;
int n,m;
db v[N],c[N],val[N];
bool cmp(db a,db b){return a>b;}
bool check(db x)
{
for(int i=1;i<=n;++i)
val[i]=v[i]-x*c[i];
sort(val+1,val+n+1,cmp);
db ans=0.0;
for(int i=1;i<=m;++i)
ans+=val[i];
return ans>=0.0;
}
int main(){
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=n;++i)cin>>v[i];
for(int i=1;i<=n;++i)cin>>c[i];
db l=0.0,r=1000.0,mid;
while(l<r&&r-l>0.00001)
{
// cout<<l<<' '<<r<<' '<<mid<<'\n';
mid=(l+r)/2.0;
if(check(mid))l=mid;
else r=mid;
}
cout<<fixed<<setprecision(3)<<l<<'\n';
return 0;
}
完结撒花。