1. 程式人生 > >[Bzoj3232]圈地遊戲(分數規劃+最小割/spfa判負環)

[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