[bzoj3232]圈地游戏【0/1分数规划】【网络流】
【题目描述】
Description
DZY家的后院有一块地,由N行M列的方格组成,格子内种的菜有一定的价值,并且每一条单位长度的格线有一定的费用。
DZY喜欢在地里散步。他总是从任意一个格点出发,沿着格线行走直到回到出发点,且在行走途中不允许与已走过的路线有任何相交或触碰(出发点除外)。记这条封闭路线内部的格子总价值为V,路线上的费用总和为C,DZY想知道V/C的最大值是多少。
Input
第一行为两个正整数n,m。
接下来n行,每行m个非负整数,表示对应格子的价值。
接下来n+1行,每行m个正整数,表示所有横向的格线上的费用。
接下来n行,每行m+1个正整数,表示所有纵向的格线上的费用。
(所有数据均按从左到右,从上到下的顺序输入,参见样例和配图)
Output
输出一行仅含一个数,表示最大的V/C,保留3位小数。
Sample Input
3 4
1 3 3 3
1 3 1 1
3 3 1 0
100 1 1 1
97 96 1 1
1 93 92 92
1 1 90 90
98 1 99 99 1
95 1 1 1 94
1 91 1 1 89
1 3 3 3
1 3 1 1
3 3 1 0
100 1 1 1
97 96 1 1
1 93 92 92
1 1 90 90
98 1 99 99 1
95 1 1 1 94
1 91 1 1 89
Sample Output
1.286
HINT
Source
【题解】 看到求比值就知道这题一定要0/1分数规划,关键是怎么判断答案是否可行。
题目可以转换为最大全闭合子图的模型,
原点S 往每个点连边,流量为这个点的权值
相邻的点互相连边,流量为这两个点夹着的割线的费用。
在最外圈的点往T连边,流量为这个点所靠边界的费用。
当前答案=权值和-最大流
直观感受,割掉与S的边相当于不选这个点,割掉相邻个点的边相当于一个选一个不选,割掉连向T的边说明选这个边界上的点。
/* -------------- user Vanisher problem bzoj-3232 ----------------*/ # include <bits/stdc++.h> # define ll long long # define inf 1e9 # define eps 1e-5 # define N 110 # define M 1000100 using namespace std; int read(){ int tmp=0, fh=1; char ch=getchar(); while (ch<'0'||ch>'9'){if (ch=='-') fh=-1; ch=getchar();} while (ch>='0'&&ch<='9'){tmp=tmp*10+ch-'0'; ch=getchar();} return tmp*fh; } struct node{ int data,next,re; double l; }e[M]; int head[N*N],place,S,T,dis[N*N],q[N*N],now[N*N],n,m,p[N][N],w[N][N],sum,cx[N][N],cy[N][N]; void build(int u, int v, double l){ e[++place].data=v; e[place].next=head[u]; head[u]=place; e[place].l=l; e[place].re=place+1; e[++place].data=u; e[place].next=head[v]; head[v]=place; e[place].l=0; e[place].re=place-1; } void bfs(){ for (int i=1; i<=T; i++) dis[i]=inf; int pl=1, pr=1; q[1]=S; while (pl<=pr){ int x=q[pl++]; for (int ed=head[x]; ed!=0; ed=e[ed].next) if (dis[e[ed].data]==inf&&e[ed].l>=eps) dis[e[ed].data]=dis[x]+1, q[++pr]=e[ed].data; } } double dfs(int x, double flow){ if (x==T) return flow; double sum=0; for (int ed=now[x]; ed!=0; ed=e[ed].next) if (e[ed].l>=eps&&dis[e[ed].data]==dis[x]+1){ double tmp=dfs(e[ed].data,min(flow,e[ed].l)); sum=sum+tmp; flow=flow-tmp; e[ed].l-=tmp; e[e[ed].re].l+=tmp; if (flow<eps){ now[x]=ed; return sum; } } now[x]=0; return sum; } double dinic(){ double sum=0; for (bfs(); dis[T]!=inf; bfs()){ for (int i=S; i<=T; i++) now[i]=head[i]; sum=sum+dfs(S,inf); } return sum; } double check(double d){ place=0; memset(head,0,sizeof(head)); for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) build(S,p[i][j],w[i][j]); for (int i=1; i<=n-1; i++) for (int j=1; j<=m; j++) build(p[i][j],p[i+1][j],cx[i+1][j]*d); for (int i=2; i<=n; i++) for (int j=1; j<=m; j++) build(p[i][j],p[i-1][j],cx[i][j]*d); for (int i=1; i<=n; i++) for (int j=1; j<=m-1; j++) build(p[i][j],p[i][j+1],cy[i][j+1]*d); for (int i=1; i<=n; i++) for (int j=2; j<=m; j++) build(p[i][j],p[i][j-1],cy[i][j]*d); for (int i=1; i<=n; i++){ build(p[i][1],T,cy[i][1]*d); build(p[i][m],T,cy[i][m+1]*d); } for (int i=1; i<=m; i++){ build(p[1][i],T,cx[1][i]*d); build(p[n][i],T,cx[n+1][i]*d); } return sum-dinic(); } int main(){ n=read(), m=read(); S=0; for (int i=1; i<=n; i++) for (int j=1; j<=m; j++){ w[i][j]=read(), sum=sum+w[i][j]; p[i][j]=++place; } T=++place; for (int i=1; i<=n+1; i++) for (int j=1; j<=m; j++) cx[i][j]=read(); for (int i=1; i<=n; i++) for (int j=1; j<=m+1; j++) cy[i][j]=read(); double pl=0, pr=10000.00, ans=0; while (pl<=pr){ double mid=(pl+pr)/2; if (check(mid)>eps) ans=mid, pl=mid+eps; else pr=mid-eps; } printf("%.3lf",ans); return 0; }