天天看点

bzoj 3232: 圈地游戏 01分数规划

        首先二分答案(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