分数规划笔记
前言
分数规划是来求一个分式的极值
形象点就是已知
的极值,其中
显然可以二分求解,设当前二分值为
若
即
前置知识
二分
CF1486D Max Median
二分练手题,与分数规划无关
给定一个长度为
因为答案具有单调性,所以考虑二分
设二分值为
可以构造序列
前缀和再前缀
代码:
#include<bits/stdc++.h> #define ll long long #define sf scanf #define pf printf #define pb push_back #define cmax(x,y) x=max(x,y); #define cmin(x,y) x=min(x,y); #define ull unsigned long long #define drep(i,x,y) for(int i=x;i>=y;i--) #define rep(i,x,y) for(int i=x;i<=y;i++) #define IOS ios::sync_with_stdio(false) using namespace std; inline ll in(){ ll x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') (ch=='-'?f=-1:1),ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f; } ll n,k,ls,rs,a[200020],b[210000]; inline bool check(ll mid){ rep(i,1,n) b[i]=(a[i]>=mid?1:-1),b[i]+=b[i-1]; ll mx=0,ip=0; rep(i,k,n){ if(b[i]-mx>0) { ls=ip+1; rs=i; return 1; } if(mx>b[i-k+1]) mx=min(b[i-k+1],mx),ip=i-k+1; } return 0; } int main(){ n=in(); k=in(); rep(i,1,n) a[i]=in(); ll l=-1e9,r=1e9,mid,ans=1e9; while(l<=r){ mid=(l+r)>>1; if(check(mid)) ans=mid,l=mid+1; else r=mid-1; } check(ans); // cout<<ans<<' '<<ls<<' '<<rs<<'\n'; sort(a+ls,a+rs+1); cout<<ans; return 0; }
代码有点乱,见谅
01分数规划
没找到原题,bzoj有,链接不给了
求
的最大值,其中
并且满足
先二分
所以贪心选
代码:
#include<bits/stdc++.h> #define ll long long #define sf scanf #define pf printf #define pb push_back #define cmax(x,y) x=max(x,y); #define cmin(x,y) x=min(x,y); #define ull unsigned long long #define drep(i,x,y) for(int i=x;i>=y;i--) #define rep(i,x,y) for(int i=x;i<=y;i++) #define IOS ios::sync_with_stdio(false) using namespace std; inline ll in(){ ll x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') (ch=='-'?f=-1:1),ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f; } ll n,k; double a[200020],b[210000]; inline bool check(double mid){ vector<double> vec; double v=0; vec.clear(); rep(i,1,n) vec.pb(a[i]-b[i]*mid); sort(vec.begin(),vec.end(),greater<>()); rep(i,0,k-1) v+=vec[i]; return v>=1e-5; } int main(){ n=in(); k=in(); rep(i,1,n) sf("%lf",&a[i]); rep(i,1,n) sf("%lf",&b[i]); double l=-1e9,r=1e9,mid,ans=1e9; while((r-l)>1e-9){ mid=(l+r)/2; if(check(mid)) ans=mid,l=mid; else r=mid; } pf("%.4lf\n",ans); return 0; }
[ABC236E] Average and Median
请求出:
-
所有选择方案中,被选中的数字平均值的最大值,误差在
以内视为正确; -
所有选择方案中,被选中的数字中位数的的最大值。在这里,偶数
个数的中位数视作第 小的数。
01规划很难弄,于是写了个二分套 DP
求最大平均值和最大中位数
对于最大平均值可以构造
然后
则
判断
求最大中位数,一样的套路
可以构造
然后
则
判断
代码:
#include<bits/stdc++.h> #define ll long long #define sf scanf #define pf printf #define pb push_back #define cmax(x,y) x=max(x,y); #define cmin(x,y) x=min(x,y); #define ull unsigned long long #define drep(i,x,y) for(int i=x;i>=y;i--) #define rep(i,x,y) for(int i=x;i<=y;i++) #define IOS ios::sync_with_stdio(false) using namespace std; inline ll in(){ ll x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') (ch=='-'?f=-1:1),ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f; } ll n,k; ll a[200020],c[200020],f[200020][2]; double b[210000],dp[200020][2]; inline bool check(double mid){ rep(i,1,n) b[i]=a[i]-mid; dp[1][1]=b[1]; dp[1][0]=0; rep(i,2,n){ dp[i][1]=max(dp[i-1][0],dp[i-1][1])+b[i]; dp[i][0]=dp[i-1][1]; } double v=max(dp[n][0],dp[n][1]); return v>=0; } inline bool chk(ll v){ rep(i,1,n) c[i]=(a[i]>=v?1:-1); ll mi=0; f[1][1]=c[1]; f[1][0]=0; rep(i,2,n){ f[i][1]=max(f[i-1][1],f[i-1][0])+c[i]; f[i][0]=f[i-1][1]; if(i==n&&max(f[i][1],f[i][0])>0) return 1; } return 0; } inline void sol(){ ll l=-1e9,r=1e9,mid,ans; while(l<=r){ mid=(l+r)>>1; if(chk(mid)) l=mid+1,ans=mid; else r=mid-1; } pf("%lld\n",ans); } int main(){ n=in(); rep(i,1,n) sf("%lld",&a[i]); double l=-1e9,r=1e9,mid,ans=1e9; while((r-l>=1e-6)){ mid=(l+r)/2; if(check(mid)) ans=mid,l=mid; else r=mid; } pf("%.4lf\n",ans); sol(); return 0; }
P4377 [USACO18OPEN] Talent Show G
简化题意就是求
求
的最大值,其中
多了个额外条件
数据范围
分数规划套01背包
二分出
先构造
显然是求体积
但
注意到我们答案是取
而
最终复杂度
代码:
#include<bits/stdc++.h> #define ll long long #define sf scanf #define pf printf #define pb push_back #define cmax(x,y) x=max(x,y); #define cmin(x,y) x=min(x,y); #define ull unsigned long long #define drep(i,x,y) for(int i=x;i>=y;i--) #define rep(i,x,y) for(int i=x;i<=y;i++) #define IOS ios::sync_with_stdio(false) using namespace std; inline ll in(){ ll x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') (ch=='-'?f=-1:1),ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f; } ll n,v,w[5050],t[5050]; double c[5050],dp[50050]; inline bool check(double mid){ rep(i,1,n) c[i]=1.0*t[i]-w[i]*mid; rep(i,1,3000) dp[i]=-1e18; rep(i,1,n){ drep(j,v,0){ ll sz=min(w[i]+j,v); dp[sz]=max(dp[sz],dp[j]+c[i]); } } double ret=dp[v]; return ret>=0; } int main(){ n=in(); v=in(); rep(i,1,n) w[i]=in(),t[i]=in(); double l=-1e6,r=1e6,mid,ans=0; while(r-l>=1e-8){ mid=(l+r)/2; if(check(mid)) ans=mid,l=mid; else r=mid; } ll an=ans*1000; cout<<an; return 0; }
P4322 [JSOI2016] 最佳团体
分数规划套树上背包
简要题意,一颗以
的最大值
分数规划
其实原题题意是很多棵树(森林),但有做选课那道题的经验,可以直接建一个超级根
然后二分
做树上背包,
注意如果
至于复杂度为什么是
我的实现比较奇怪,是先把儿子的值做背包,再加上当前节点的值
代码:
#include<bits/stdc++.h> #define ll long long #define sf scanf #define pf printf #define pb push_back #define cmax(x,y) x=max(x,y); #define cmin(x,y) x=min(x,y); #define ull unsigned long long #define drep(i,x,y) for(int i=x;i>=y;i--) #define rep(i,x,y) for(int i=x;i<=y;i++) #define IOS ios::sync_with_stdio(false) using namespace std; inline ll in(){ ll x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') (ch=='-'?f=-1:1),ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f; } int n,k,sz[2505],s[2505],p[2505]; double mid,ans,c[2505],dp[2505][2505]; vector<int> e[2505]; void dfs(int u){ sz[u]=1; c[u]=1.0*p[u]-mid*s[u]; for(int v:e[u]){ dfs(v); sz[u]+=sz[v]; } rep(i,1,sz[u]) dp[u][i]=-1e8; int now=0; for(int v:e[u]){ now+=sz[v]; drep(i,now,0){ drep(j,min(sz[v],i),1){ dp[u][i]=max(dp[u][i],dp[v][j]+dp[u][i-j]); } } } if(u) drep(i,sz[u],1) dp[u][i]=dp[u][i-1]+c[u]; } int main(){ k=in(); n=in(); rep(i,1,n){ s[i]=in(); p[i]=in(); int fa=in(); e[fa].pb(i); } double l=-1e9,r=1e9; while((r-l)>1e-6){ mid=(l+r)/2; dfs(0); bool ck=0; ck=(dp[0][k]>=0); // pf("mid: %.3lf %.3lf \n",mid,dp[0][k]); if(ck) l=mid,ans=mid; else r=mid; } pf("%.3lf\n",ans); return 0; }
P3199 [HNOI2009] 最小圈
求有向图边权平均值最小的环
分数规划求最小均值环
还是二分
然后我们把边权全部减去
就是如果有负环,那么就说明有解 (很显然但是很有意思)
还有要注意的点, 直接 spfa 很容易跑满
我们需要使用
大概如下
bool spfa(int u){ vis[u]=1; for(v:e[u]){ if(d[v]>d[u]+w-mid){ if(vis[v]) return true; d[v]=d[u]+w-mid; spfa(v); } } vis[u]=0; return false; }
因为只用判负环,所以我们保证
初始化所有
然后就简单了
代码:
#include<bits/stdc++.h> #define ll long long #define sf scanf #define pf printf #define pb push_back #define cmax(x,y) x=max(x,y); #define cmin(x,y) x=min(x,y); #define ull unsigned long long #define drep(i,x,y) for(int i=x;i>=y;i--) #define rep(i,x,y) for(int i=x;i<=y;i++) #define IOS ios::sync_with_stdio(false) using namespace std; inline ll in(){ ll x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') (ch=='-'?f=-1:1),ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f; } int n,m,ok; bitset<3030> vis,cle; double mid,ans,d[3030]; struct node{ int v; double w; }; vector<node> e[3030]; void check(int u){ if(ok) return; vis[u]=1; for(node o:e[u]){ int v=o.v; if(d[v]>d[u]-mid+o.w){ if(vis[v]){ ok=1; return; } d[v]=d[u]-mid+o.w; check(v); } if(ok) return; } vis[u]=0; } int main(){ n=in(); m=in(); while(m--){ int u=in(),v=in(); double w; sf("%lf",&w); e[u].pb({v,w}); } double l=-1e6,r=1e6; while((r-l)>1e-10){ mid=(l+r)/2; vis&=cle; rep(i,1,n) d[i]=0; ok=0; rep(i,1,n) check(i); if(ok) ans=mid,r=mid; else l=mid; } pf("%.8lf\n",l); return 0; }
P2868 [USACO07DEC] Sightseeing Cows G
一年前学最短路做过?但真没印象了
简要题意:求点权和除以边权和最大的简单环
跟最小圈那题差不多,只不过改了点
因为是个环,所以可以把点权转移到边上,则
然后同样判断一下是否有负环即可
代码:
#include<bits/stdc++.h> #define ll long long #define sf scanf #define pf printf #define pb push_back #define cmax(x,y) x=max(x,y); #define cmin(x,y) x=min(x,y); #define ull unsigned long long #define drep(i,x,y) for(int i=x;i>=y;i--) #define rep(i,x,y) for(int i=x;i<=y;i++) #define IOS ios::sync_with_stdio(false) using namespace std; inline ll in(){ ll x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') (ch=='-'?f=-1:1),ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f; } int n,m,ok; bitset<3030> vis,cle; double mid,ans,w[3030],d[3030]; struct node{ int v; double w; }; vector<node> e[3030]; void check(int u){ if(ok) return; vis[u]=1; for(node o:e[u]){ int v=o.v; double g=w[u]-o.w*mid; if(d[v]>d[u]-g){ if(vis[v]){ ok=1; return; } d[v]=d[u]-g; check(v); } if(ok) return; } vis[u]=0; } int main(){ n=in(); m=in(); rep(i,1,n) w[i]=in(); while(m--){ int u=in(),v=in(); double w; sf("%lf",&w); e[u].pb({v,w}); } double l=-1e6,r=1e6; while((r-l)>1e-10){ mid=(l+r)/2; vis&=cle; rep(i,1,n) d[i]=0; ok=0; rep(i,1,n) check(i); if(ok) ans=mid,l=mid; else r=mid; } pf("%.2lf\n",ans); return 0; }
P6087 [JSOI2015] 送礼物
分数规划使用单调队列优化决策
神仙题,调了很久(也就3h)
简要题意:求一个权值最大的区间
满足
并且
首选分数规划,但是发现
当区间长度大于
为什么,贪心的想,本来你的决策长度为
所以分子的值没变,分母反而变大了,就不是最优的了。
然后我们分类讨论
-
,所以
即
单调队列维护
的最小值 -
,所以
即
单调队列维护
的最大值
会有很多不合法的情况,但是很明显推出来值不是最优的
然后考虑长度等于
代码:
#include<bits/stdc++.h> #define ll long long #define sf scanf #define pf printf #define pb push_back #define cmax(x,y) x=max(x,y); #define cmin(x,y) x=min(x,y); #define ull unsigned long long #define drep(i,x,y) for(int i=x;i>=y;i--) #define rep(i,x,y) for(int i=x;i<=y;i++) #define IOS ios::sync_with_stdio(false) #define nx 50005 using namespace std; inline ll in(){ ll x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') (ch=='-'?f=-1:1),ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f; } int T,n,k,L,R; ll a[nx+5],q[nx+5],h,t,q2[nx+5],h2,t2; /* c[i]-c[j]>=(i-j+k)*mid c[j]-c[i]>=(i-j+k)*mid c[i]-i*mid-c[j]+j*mid>=k*mid */ double mid,ans,c[nx+5],c2[nx+5]; inline bool check(){ rep(i,1,n) c[i]=a[i]*1.0-mid*i,c2[i]=a[i]*1.0+mid*i; h2=h=1,t=t2=0; rep(i,L,n){ while(h<=t&&q[h]<i-R+1) ++h; while(h2<=t2&&q2[h2]<i-R+1) ++h2; while(h<=t&&c[q[t]]>c[i-L+1]) --t; while(h2<=t2&&c2[q2[t2]]<c2[i-L+1]) --t2; q2[++t2]=q[++t]=i-L+1; // pf("now : l: %d r: %d %.3lf\n",q[h],i,(c[i]-c[q[h]])); if(h<=t&&(c[i]-c[q[h]])>=mid*k) return true; // pf("now : l: %d r: %d %.3lf\n",q2[h2],i,(c2[q2[h2]]-c2[i])); if(h2<=t2&&(c2[q2[h2]]-c2[i])>=mid*k) return true; } // pf("debug false\n"); return false; } inline double sol(){ h2=h=1,t=t2=0; double ret=0; rep(i,1,n){ while(h<=t&&q[h]<i-L+1) ++h; while(h2<=t2&&q2[h2]<i-L+1) ++h2; while(h<=t&&a[q[t]]<a[i]) --t; while(h2<=t2&&a[q2[t2]]>a[i]) --t2; q[++t]=q2[++t2]=i; ret=max(ret,(a[q[h]]-a[i])*1.0/(L-1+k)); ret=max(ret,(a[i]-a[q2[h2]])*1.0/(L-1+k)); } return ret; } int main(){ T=in(); while(T--){ n=in(); k=in(); L=in(); R=in(); rep(i,1,n) a[i]=in(); double l=-1e6,r=1e6; while((r-l)>1e-7){ mid=(l+r)/2; if(check()) ans=mid,l=mid; else r=mid; // pf("-------debug-------\n"); } pf("%.4lf\n",max(ans,sol())); } return 0; }
P3705 [SDOI2017] 新生舞会
简要题意:
二分图最大匹配,且要有选择的边权值
最小
都是套路题,先二分,把边权变成
然后发现这其实是求二分图最小权匹配是否大于
但因为数据范围比较大,所以考虑费用流求最大匹配
最后复杂度
还有就是有人问是否存在正环,显然不行,因为网络流都是单向边,不会有环,唯一的反边构成的环也是
代码
#include<bits/stdc++.h> #define ll long long #define sf scanf #define pf printf #define pb push_back #define cmax(x,y) x=max(x,y); #define cmin(x,y) x=min(x,y); #define ull unsigned long long #define drep(i,x,y) for(int i=x;i>=y;i--) #define rep(i,x,y) for(int i=x;i<=y;i++) #define IOS ios::sync_with_stdio(false) using namespace std; inline ll in(){ ll x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') (ch=='-'?f=-1:1),ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f; } template<int T> struct Dinic{ struct edge{ int v,w,next; double f; }e[T*T+5]; int head[T+5],tot,now[T+5]; double d[T+5]; bool vis[T+5]; Dinic(){ tot=1; for(int i=1;i<=T;i++) head[i]=0; } inline void clear(){ tot=1; for(int i=1;i<=T;i++) head[i]=0; } inline void add(int x,int y,int c,double f){ e[++tot]={y,c,head[x],f}; head[x]=tot; e[++tot]={x,0,head[y],-f}; head[y]=tot; } int inf=2e9; inline int bfs(int s,int t){ for(int i=1;i<=T;i++) d[i]=1e18,vis[i]=0; queue<int> q; while(q.size()) q.pop(); d[s]=0; vis[s]=1; q.push(s); now[s]=head[s]; while(q.size()){ int u=q.front(); q.pop(); vis[u]=0; for(int i=now[u];i;i=e[i].next){ int v=e[i].v; if(e[i].w&&d[v]>d[u]+e[i].f){ d[v]=d[u]+e[i].f; now[v]=head[v]; if(!vis[v]) vis[v]=1,q.push(v); } } } // pf("%.6lf\n",d[t]); return d[t]<1e18; } double minf=0; bool vi[T+5]; int dinic(int u,int flow,int t){ if(u==t) return flow; int rest=flow,ret=0; vi[u]=1; for(int i=now[u];i&&rest;i=e[i].next){ int v=e[i].v; now[u]=i; if(e[i].w&&(d[v]==d[u]+e[i].f)&&!vi[v]){ int p=dinic(v,min(rest,e[i].w),t); if(!p){ d[v]=0; continue; } rest-=p; ret+=p; e[i].w-=p; e[i^1].w+=p; minf+=e[i].f*p; } } vi[u]=0; return ret; } inline int flow(int s,int t){ for(int i=1;i<=T;i++) now[i]=0; minf=0; int ret=0,w; while(bfs(s,t)) { while(1){ w=dinic(s,inf,t); ret+=w; if(!w) break; } } return ret; } }; Dinic<205> S; int n,a[105][105],b[105][105]; double mid,ans; inline bool check(){ S.clear(); rep(i,1,n){ S.add(n*2+1,i,1,0); S.add(i+n,n*2+2,1,0); rep(j,1,n){ double f=mid*b[i][j]-1.0*a[i][j]; S.add(i,j+n,1,f); } } if(S.flow(n*2+1,n*2+2)==n&&S.minf>0) return true; return false; } int main(){ n=in(); rep(i,1,n) rep(j,1,n) a[i][j]=in(); rep(i,1,n) rep(j,1,n) b[i][j]=in(); double l=0,r=1e5; while((r-l)>1e-8){ mid=(l+r)/2; if(check()) ans=mid,r=mid; else l=mid; } pf("%.6lf\n",ans); return 0; }
其实是第一次写费用流诶。
总结
一个不错的
后边继续更最小密度子图就彻底完结了!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战