[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

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),最后把所有可能损失精度的地方加优化。
代码:
#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
*/
View Code

 

 

转载于:https://www.cnblogs.com/sherrlock/p/9930194.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值