BZOJ 3232: 圈地游戏 分数规划+判负环

Posted Oncle_Ha

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BZOJ 3232: 圈地游戏 分数规划+判负环相关的知识,希望对你有一定的参考价值。

3232: 圈地游戏

Time Limit: 20 Sec  Memory Limit: 128 MB
Submit: 966  Solved: 466
[Submit][Status][Discuss]

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

 

技术分享

 

Source

jcvb提供

想法:$max(\frac{\sum D}{\sum C})$,一般都是用分数规划求。

对于一个环,某一行\列所选择的边都是偶数的。被包含的部分就像左右括号包含一样。于是这样建图:

横边:

  ①$(i,j)->(i,j+1):C_i=边花费,D_i=sum[i][j](即该列的前缀和)$

  ②$(i,j)->(i,j-1):C_i=边花费,D_i=-sum[i][j](即该列的前缀和)$

竖边:

  ①$(i,j)->(i+1,j):C_i=边花费,D_i=0$

  ②$(i,j)->(i-1,j):C_i=边花费,D_i=0$

然后判断合法,即是判断图中是否有非负环,可以权值取反后DFS_spfa求负环(这里有一个比较快的求法)。

另一个解法:考虑如果相邻的格子被同时选中,那么中间的边的费用就不会算进来了,于是变成最小割模型。

 

#include<cstdio>

typedef long long ll;
template<class T>
inline void read(T&x)
{
    x=0;bool f=0;char c=getchar();
    while((c<0||c>9)&&c!=-)c=getchar(); if(c==-)f=1,c=getchar();
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x=f?-x:x;
}
const int MAXN(60);
const double eps(1e-6);
int n,m,val[MAXN][MAXN],row[MAXN][MAXN],line[MAXN][MAXN],sum;
double ans;
struct Node{int nd,nx,v,c;}bot[MAXN*MAXN<<2];int tot,first[MAXN*MAXN];
int P(int x,int y){return (x-1)*(m+1)+y;}
void add(int a,int b,int v,int c){bot[++tot]=(Node){b,first[a],v,c};first[a]=tot;}
void build()
{
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)val[i][j]+=val[i-1][j];
    for(int i=1;i<=n+1;i++)
    for(int j=1;j<=m;j++)
        add(P(i,j),P(i,j+1),val[i-1][j],row[i][j]),
        add(P(i,j+1),P(i,j),-val[i-1][j],row[i][j]);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m+1;j++)
        add(P(i,j),P(i+1,j),0,line[i][j]),
        add(P(i+1,j),P(i,j),0,line[i][j]);
}
double dis[MAXN*MAXN],limt;
bool vis[MAXN*MAXN],flag;
void dfs(int x)
{
    vis[x]=true;
    for(int v=first[x];v;v=bot[v].nx)
    if(dis[bot[v].nd]>dis[x]+bot[v].c*limt-bot[v].v+eps)
    {
        if(vis[bot[v].nd]){flag=true;return;};
        dis[bot[v].nd]=dis[x]+bot[v].c*limt-bot[v].v;
        dfs(bot[v].nd);if(flag)return;
    }
    vis[x]=false;
}
bool ok(double mid)
{
    limt=mid; flag=false; for(int i=1;i<=(n+1)*(m+1);i++)dis[i]=vis[i]=0;
    for(int i=1;i<=(n+1)*(m+1);i++)
    {
        dfs(i);if(flag)return true;
    }
    return false;
}
int main()
{
//    freopen("C.in","r",stdin);
//    freopen("C.out","w",stdout);
    read(n);read(m);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)read(val[i][j]),sum+=val[i][j];
    for(int i=1;i<=n+1;i++)
    for(int j=1;j<=m;j++)read(row[i][j]);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m+1;j++)read(line[i][j]);
    build();
    for(double l=0,r=sum,mid;l+eps<r;)
        if(ok(mid=(l+r)/2))l=mid,ans=mid;else r=mid;
    printf("%.3lf",ans);
    return 0;
}

 

 

 

 

以上是关于BZOJ 3232: 圈地游戏 分数规划+判负环的主要内容,如果未能解决你的问题,请参考以下文章

bzoj 3232: 圈地游戏分数规划+最小割

bzoj 3232 圈地游戏 —— 01分数规划+最小割建图(最大权闭合子图)

BZOJ1486HNOI2009最小圈 分数规划 dfs判负环。

BZOJ_1486_[HNOI2009]最小圈_01分数规划

bzoj3597[Scoi2014]方伯伯运椰子 01分数规划+spfa判负环

bzoj 1690: [Usaco2007 Dec]奶牛的旅行——分数规划+spfa判负环