首先二分答案(dinkelbach?不知道行不行),然后就变成判断是否可行了。
这里有一个比较巧妙的方法,就是给圈地定一个方向,不妨为逆时针,那么向上走就相当于把它左边的加入答案;向下走就相当于把它左边的从答案中减去,然后判断是否存在负权回路即可。
另外还可以网络流建图,所有块从S连边容量为价值,然后在外面加一圈向T连边容量为边权inf,然后任意两个相邻的块(包括外面一圈)连双向边容量为边的费用。然后跑最小割从答案中减去即可。
AC代码如下:
#include<iostream> #include<cstdio> #include<cstring> #define N 55 #define M 100005 using namespace std; int m,n,cnt,h[M+5][2],s[N][N],mp[N][N][4],num[N][N]; double d[N][N],len[N][N][4]; bool bo[N][N]; const int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};//0上 1下 2左 3右 bool ok(double t){ int i,j,k,x,y; for (i=0; i<=m; i++) for (j=0; j<=n; j++) for (k=0; k<4; k++){ x=i+dx[k]; y=j+dy[k]; if (x>=0 && x<=m && y>=0 && y<=n){ len[i][j][k]=mp[i][j][k]*t; if (!k) len[i][j][k]-=s[i][j]; else if (k==1) len[i][j][k]+=s[i+1][j]; } } int head=0,tail=cnt,u=0,v=0; for (i=1; i<=cnt; i++){ h[i][0]=u; h[i][1]=v; if (v<n) v++; else{ u++; v=0; } } memset(bo,0,sizeof(bo)); memset(num,0,sizeof(num)); while (head!=tail){ head=head%M+1; x=h[head][0]; y=h[head][1]; bo[x][y]=1; for (i=0; i<4; i++){ u=x+dx[i]; v=y+dy[i]; if (u>=0 && u<=m && v>=0 && v<=n) if (d[x][y]+len[x][y][i]<d[u][v]){ d[u][v]=d[x][y]+len[x][y][i]; num[u][v]=num[x][y]+1; if (num[u][v]>cnt) return 1; if (bo[u][v]){ bo[u][v]=0; tail=tail%M+1; h[tail][0]=u; h[tail][1]=v; } } } } return 0; } int main(){ scanf("%d%d",&m,&n); cnt=(m+1)*(n+1); int i,j,x; for (i=1; i<=m; i++) for (j=1; j<=n; j++){ scanf("%d",&x); s[i][j]=s[i][j-1]+x; } for (i=0; i<=m; i++) for (j=1; j<=n; j++){ scanf("%d",&x); mp[i][j][2]=mp[i][j-1][3]=x; } for (i=1; i<=m; i++) for (j=0; j<=n; j++){ scanf("%d",&x); mp[i][j][0]=mp[i-1][j][1]=x; } double l=0,r=m*n*100,mid; while (l+1e-6<r){ mid=(l+r)/2; if (ok(mid)) l=mid; else r=mid; } printf("%.3f\n",(l+r)/2); return 0; }
by lych
2016.4.13