[笔记乱写]0/1分数规划
第一次写这类blog?其实早就想把知识总结一下但是真的没有时间啊喂
感觉这东西比较实用且简易所以口胡一篇
所谓分数规划,就是求最优比率,比如一个分数的最大值。
经典模型:
给定若干对整数$a_i,b_i$,要求从中选出几对,使$\frac{\sum a_i}{\sum b_i}$最大。
对于这个问题,我们不妨xjb猜随意选取一个值$L$,然后考虑:
是否存在一组解,满足$\sum a_i-L*b_i \ge 0$
把上面那个鬼畜柿子化一下,可得$\sum a_i-\sum L*b_i \ge 0$,
最终变成$\frac{\sum a_i}{\sum b_i}\ge L$
那如果符合条件的解存在的话,就说明存在比L更大的比率,即L比我们所求的最大值要小。
那要是不存在呢?L比最大值还大呗。
是不是很熟悉?这玩意显然可以二分答案搞吧,因为L与解的关系存在单调性。
那么怎么判定$\sum a_i-L*b_i \ge 0$是否有解呢?算一下$\sum a_i-L*b_i$的最大值看它正负不就好了。
这很好求啊,因为$a_i-L*b_i$可以直接算的,只要看一下哪些取哪些不取就能得道最值了。
所以遇到这类问题直接二分答案,判断$\sum a_i-mid*b_i$的最大值是否非负,是就l=mid,否则r=mid(一定要注意实数范围二分与整数二分的不同)
最后的l就是最优比例。
丢一道例题[bzoj4753]最优团体
挺裸的分数规划,不过check需要做树上依赖背包,每次拍成dfs序$O(nK)$dp即可。
#include<cstdio> #include<iostream> #include<cstring> using namespace std; const int N=2505; const double eps=1e-5,inf=19260817.233; int K,n,fa[N]; double now[N],dp[N][N],w[N],val[N],maxx=-inf; int to[N<<1],nxt[N<<1],head[N],tot; int dfn[N],rev[N],num,size[N]; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x*f; } void add(int x,int y) { to[++tot]=y; nxt[tot]=head[x]; head[x]=tot; } void dfs(int x) { size[x]=1; dfn[x]=num; rev[num++]=x; for(int i=head[x];i;i=nxt[i]) dfs(to[i]),size[x]+=size[to[i]]; } double DP(double x) { for(int i=1;i<=n;i++) now[i]=val[rev[i]]-x*w[rev[i]]; for(int i=1;i<=n+1;i++) for(int j=0;j<=K+1;j++) dp[i][j]=-inf; for(int i=0;i<=n;i++) for(int j=0;j<=min(i,K+1);j++) dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]+now[i]), dp[i+size[rev[i]]][j]=max(dp[i+size[rev[i]]][j],dp[i][j]); return dp[n+1][K+1]; } int main() { K=read();n=read(); for(int i=1;i<=n;i++) { w[i]=(double)read(); val[i]=(double)read(),maxx=max(maxx,val[i]); fa[i]=read(); add(fa[i],i); } dfs(0); double l=0.0,r=maxx; while(r-l>eps) { double mid=(l+r)/2.0; if(DP(mid)>=eps)l=mid; else r=mid; } printf("%.3lf",l); return 0; }
兴许青竹早凋,碧梧已僵,人事本难防。