• [BZOJ3232]圈地游戏


    bzoj

    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
    

    Sample Output

    1.286
    

    Hint

    sol

    肯定是先分数规划一波啦。
    这样一来选出的点产生正权,选出的边产生负权,可以跑一个最大权闭合子图判断是否存在一种选法使权值和大于零。
    具体的建图:

    1、源点(S)向每个点连点权的边,表示若不选这个点需要付出点权的代价。
    2、相邻点之间连(mid*)边权的双向边,表示若两个点的选取状态不同,则它们中间的那条边就一定被经过了,需要付出(mid*)边权的代价。
    3、边界上的点向汇点(T)(mid*)边权的边,表示这个点若要选就需要走过边界上的这条边。

    code

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    #define P(i,j) ((i-1)*m+j)
    const int N = 55;
    const double eps = 1e-5;
    struct edge{int to,nxt;double w;}a[N*N*N];
    int n,m,S,T,head[N*N],cnt=1,dep[N*N],cur[N*N];
    double sum,val[N][N],h[N][N],z[N][N];
    queue<int>Q;
    void link(int u,int v,double w){
    	a[++cnt]=(edge){v,head[u],w};head[u]=cnt;
    	a[++cnt]=(edge){u,head[v],0};head[v]=cnt;
    }
    bool bfs(){
    	memset(dep,0,sizeof(dep));
    	dep[S]=1;Q.push(S);
    	while (!Q.empty()){
    		int u=Q.front();Q.pop();
    		for (int e=head[u];e;e=a[e].nxt)
    			if (a[e].w>eps&&!dep[a[e].to])
    				dep[a[e].to]=dep[u]+1,Q.push(a[e].to);
    	}
    	return dep[T];
    }
    double dfs(int u,double f){
    	if (u==T) return f;
    	for (int &e=cur[u];e;e=a[e].nxt)
    		if (a[e].w>eps&&dep[a[e].to]==dep[u]+1){
    			double tmp=dfs(a[e].to,min(a[e].w,f));
    			if (tmp>eps) {a[e].w-=tmp;a[e^1].w+=tmp;return tmp;}
    		}
    	return 0;
    }
    double dinic(){
    	double res=0;
    	while (bfs()){
    		for (int i=1;i<=T;++i) cur[i]=head[i];
    		while (233){
    			double tmp=dfs(S,1e9);
    			if (tmp<=eps) break;res+=tmp;
    		}
    	}
    	return res;
    }
    bool check(double mid){
    	memset(head,0,sizeof(head));cnt=1;
    	for (int i=1;i<=n;++i)
    		for (int j=1;j<=m;++j)
    			link(S,P(i,j),val[i][j]);
    	for (int i=1;i<n;++i)
    		for (int j=1;j<=m;++j)
    			link(P(i,j),P(i+1,j),mid*h[i][j]),link(P(i+1,j),P(i,j),mid*h[i][j]);
    	for (int i=1;i<=n;++i)
    		for (int j=1;j<m;++j)
    			link(P(i,j),P(i,j+1),mid*z[i][j]),link(P(i,j+1),P(i,j),mid*z[i][j]);
    	for (int i=1;i<=m;++i) link(P(1,i),T,mid*h[0][i]),link(P(n,i),T,mid*h[n][i]);
    	for (int i=1;i<=n;++i) link(P(i,1),T,mid*z[i][0]),link(P(i,m),T,mid*z[i][m]);
    	return sum-dinic()>eps;
    }
    int main(){
    	n=gi();m=gi();S=n*m+1;T=S+1;
    	for (int i=1;i<=n;++i) for (int j=1;j<=m;++j) val[i][j]=gi(),sum+=val[i][j];
    	for (int i=0;i<=n;++i) for (int j=1;j<=m;++j) h[i][j]=gi();
    	for (int i=1;i<=n;++i) for (int j=0;j<=m;++j) z[i][j]=gi();
    	double l=0,r=1e9;
    	while (r-l>eps){
    		double mid=(l+r)/2;
    		if (check(mid)) l=mid;else r=mid;
    	}
    	printf("%.3lf
    ",l);return 0;
    }
    
  • 相关阅读:
    汇编10:CALL和RET指令
    汇编09:转移指令的原理
    汇编08:数据处理的两个基本问题
    汇编07:定位内存地址的方法
    汇编06:包含多个段的程序
    汇编05:[BX]和loop指令
    汇编04:第一个汇编程序
    汇编03:寄存器
    C#版的mongodb最新的官方驱动2.4.0版本
    如何教你看懂复杂的正则表达式
  • 原文地址:https://www.cnblogs.com/zhoushuyu/p/9082729.html
Copyright © 2020-2023  润新知