01分数规划(二分答案)

01分数规划

基础理解

简化:即对于 ∑ a [ i ] / ∑ b [ i ] \sum{a[i]}/\sum{b[i]} a[i]/b[i],求最值。

圈地游戏

solution:

首先我们需要将面积映射到边上,然后找到一条回路。
巧妙:我们会发现对于竖直的边或者是水平的边,一定是成对出现,两两相对,不同的边对之间夹着的面积不会重复,所以我们可以人为的修改边权,例如我们将面积映射到竖直方向的边上,对于逆时针旋转的方向,将向下的边权加上前缀面积的负值,向上的边加上前缀面积的正值。
每次我们二分一个 m i d mid mid,去验证 ∑ S − ∑ C ∗ m i d > = 0 \sum S - \sum C*mid >=0 SCmid>=0
所以我们去验证当前的图中是否有正权环, s p f a spfa spfa即可

#pragma GCC optimize(3)
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define ll long long
#define D double
using namespace std;
const int N = 105;
const D eps = 1e-4;
int n,m,id[N][N],cid,tmp;
D sum[N][N],a[N][N],b[N][N],len[N*N],dis[N*N],l,r,ans;
int en[N*N],nex[N*N],lst[N*N],tot;
void add(int x,int y,D z){
	en[++tot]=y;nex[tot]=lst[x];lst[x]=tot;len[tot]=z;
}
void init(D mid){
	tot=0;memset(lst,0,sizeof lst);
	for(int i=0;i<=n;i++)
	for(int j=0;j<m;j++){
		D x=a[i][j];
		add(id[i][j],id[i][j+1],-x*mid);
		add(id[i][j+1],id[i][j],-x*mid);
	}
	for(int i=0;i<n;i++)
	for(int j=0;j<=m;j++){
		D x=b[i][j];
		add(id[i][j],id[i+1][j],-x*mid-sum[i+1][j]);
		add(id[i+1][j],id[i][j],-x*mid+sum[i+1][j]);
	}
}
queue<int>Q;
int in[N*N],cnt[N*N];
bool spfa(){
	for(int i=1;i<=cid;i++)dis[i]=-1e9,cnt[i]=0;
	Q.push(1);dis[1]=0;
	while(Q.size()){
		int u=Q.front();Q.pop();in[u]=0;
		for(int i=lst[u];i;i=nex[i]){
			int v=en[i];
			if(dis[v]<dis[u]+len[i]){
				if(++cnt[v]>=tmp)return 1;
				dis[v]=dis[u]+len[i];
				if(!in[v])Q.push(v),in[v]=1;
			}
		}
	}return 0;
}
signed main()
{
	scanf("%d%d",&n,&m);tmp=min(n*m,505);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)
	scanf("%lf",&sum[i][j]),sum[i][j]+=sum[i][j-1];
	for(int i=0;i<=n;i++)
	for(int j=0;j<=m;j++)id[i][j]=++cid;
	for(int i=0;i<=n;i++)
	for(int j=0;j<m;j++)scanf("%lf",&a[i][j]);
	for(int i=0;i<n;i++)
	for(int j=0;j<=m;j++)scanf("%lf",&b[i][j]);
	l=0,r=1e6;
	while(l+eps<=r){
		D mid=(l+r)/2.00;
		init(mid);
		if(spfa())ans=mid,l=mid;
		else r=mid;
	}printf("%.3lf\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值