[Bzoj3232]圈地游戏(分数规划+最小割/spfa判负环)
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
这道题调了很久,为了复习dinic特意在写这题前先去写一遍格子取数(然后调了一晚上)。
不难发现这道题具有分数规划的性质。我们要求格子的和处以边的和。具体的分数规划可以参考我的另一篇博客 链接
二分答案mid值,
然后如何判断 sum_d(被圈进去的格子总权) - mid×c(边权×预计答案,不理解请重新学习分数规划)<0 就有两种做法。
一种是差分,然后以边为结点建图,沿横边正向走为加,反向走为减,最后就是判图中是否有负环。这里不做解释。
我用的是最小割的解法。
我们考虑如果以界外为汇点,界内所有格子连向源点,格子间都连上边,边的流量就为mid*c 。那么最小割就能满足我们选择了单独的一个封闭区间,而最小割的值就是mid×c+lose_d(不被选用的点权和),我们用点权总和减去最小割的值,就是sum_d-mid*c。这个值如果大于0,就把mid往大了取,如果小于0,就往小了取。
注意精度!!!我被卡了精度wa了三次(我甚至上dbzoj下了数据,然后就新人求助,本机ac,提交wa),最后把所有可能损失精度的地方加优化。
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #define maxn 55 #define INF 99999999.999999 #define eps 1e-6 using namespace std; inline void read(int &x){ x=0;int 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();} x*=f; } int N,M; double ans,sum; int p(int a,int b){ return (a-1)*M+b; } int map[maxn][maxn]; int up[maxn][maxn],ls[maxn][maxn]; struct node{ int nex,to; double w; }edge[500000]; int head[3000],tot; int S,T; inline void insert(int from,int to,double w){ edge[tot].nex=head[from]; head[from]=tot; edge[tot].to=to; edge[tot].w=w; tot++; edge[tot].nex=head[to]; head[to]=tot; edge[tot].to=from; edge[tot].w=0; tot++; } int dept[3000]; bool bfs(){ memset(dept,0,sizeof(dept)); queue<int>que; que.push(S); dept[S]=1; int x; while(!que.empty()){ x=que.front(); que.pop(); for(int i=head[x];i!=-1;i=edge[i].nex) if(edge[i].w>eps&&!dept[edge[i].to]){ dept[edge[i].to]=dept[x]+1; que.push(edge[i].to); if(edge[i].to==T) return 1; } } return 0; } double dfs(int x,double f){ if(x==T) return f; double tmp=f; for(int i=head[x];i!=-1;i=edge[i].nex) if(edge[i].w>eps&&dept[edge[i].to]==dept[x]+1){ double cal=dfs(edge[i].to,min(tmp,edge[i].w)); if(cal<eps) dept[edge[i].to]=0; edge[i].w-=cal; edge[i^1].w+=cal; tmp-=cal; if(tmp<eps) break ; } return f-tmp; } void dinic(){ while(bfs()){ //for(int i=1;i<=N*M;i++) // cout<<dept[i]<<" "; //cout<<endl; //printf("%.3f\n",ans); //system("pause"); ans+=dfs(S,INF); } } bool check(double x){ memset(head,-1,sizeof(head)); tot=0;ans=0.0; for(int i=1;i<=N;i++) for(int j=1;j<=M;j++) insert(S,p(i,j),1.0*map[i][j]); for(int i=1;i<N;i++) for(int j=1;j<=M;j++){ insert(p(i,j),p(i+1,j),x*up[i][j]); insert(p(i+1,j),p(i,j),x*up[i][j]); } for(int i=1;i<=N;i++) for(int j=1;j<M;j++){ insert(p(i,j),p(i,j+1),x*ls[i][j]); insert(p(i,j+1),p(i,j),x*ls[i][j]); } for(int i=1;i<=M;i++){ insert(p(1,i),T,x*up[0][i]); insert(p(N,i),T,x*up[N][i]); } for(int i=1;i<=N;i++){ insert(p(i,1),T,x*ls[i][0]); insert(p(i,M),T,x*ls[i][M]); } dinic(); //cout<<sum<<" "; //printf("%.3f\n",ans); return sum-ans>eps; } int main(){ //freopen("10.in","r",stdin); read(N);read(M); S=0;T=N*M+1; for(int i=1;i<=N;i++) for(int j=1;j<=M;j++) read(map[i][j]),sum+=1.0*map[i][j]; for(int i=0;i<=N;i++) for(int j=1;j<=M;j++) read(up[i][j]); for(int i=1;i<=N;i++) for(int j=0;j<=M;j++) read(ls[i][j]); double l=0.0,r=N*M*100.0,mid; while(r-l>eps) { mid=(l+r)*0.5; //cout<<l<<" "<<r<<" "<<mid<<endl; if(check(mid)) l=mid; else r=mid; } printf("%.3lf",l); return 0; } /* 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 */