网络流24题--方格取数问题

来源:互联网 发布:centos 6.5 ftp客户端 编辑:程序博客网 时间:2024/05/04 17:54
若有疏漏,敬请指出不足之处,谢谢!!!

最近刷了网络流的不少题目,,,所以决定总结一下关于方格取数问题的基本做法。

模版题 Hdu 1565

题意

大概的题意是:
给你一个n*n的格子的棋盘,每个格子里面有一个非负数。
从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大。

Hdu 1565就是这样的题目。

通常做法

对于这类题目,甚至可以推广到只要有方格类的。我们可以考虑将其黑白染色,如图,
这里写图片描述
假设其点权为2,1,3,4,作为一个例子
方格就转化为二分图,相邻的格子连边,求最大点权独立集。

知识原理和定义

最大点权独立集的定义是,在二分图中选定一些点,使这些点不被边直接相连的同时满足点权和最大。
最小点权覆盖是指,在二分图中选定一些点,使这些点能够覆盖所有边,即满足所有边的其中一个点是选定点的一个。
众所周知(在这里就不证明了),最大点权独立集等于点权和减去最小点权覆盖,你们可以试验一下。
也就是在二分图中选定若干点后满足最小点权覆盖,没选定的点即构成最大点权独立集。
对于解决最小点权覆盖,采用最小割方式解决,网络流通常是把限制条件转化成边权来跑。

建图

所以构造源点,连接源点和被染成黑色的点,边权为该点的点权,相邻的点边权为inf
如图:
这里写图片描述

建图思路

然后求最小割,就是最小点权覆盖
证明如下:
我们知道如果存在最小割,那么任何形如S-u-v-T的线路上必定是不通的(即有割从中经过,否则不满足割的性质)
因为是求最小割,而u-v的边权是inf,因此割不会从此经过,也就是从
S-u,v-T中选一个经过。

选了一条边之后,边上除了源点和汇点的另外一个点即视为选中,
所以当有割时,任意一个S-u-v-T的线路肯定是u或者v被选中,
也就是满足所有边都覆盖。
当是最小割时,点权覆盖也为最小。

所以最小割=最小点权覆盖,
同时又最大流最小割定理可知,最大流=最小割,所以这类题目可以跑一下网络流最大流来完成。

AC代码

Hdu 1565代码如下:

#include <iostream>#include <cstdio>#include <queue>#include <cstring>#include <cstdlib>using namespace std;#define MAXN 21#define inf 0x7fffffffstruct Edge{    int v,w,nxt;}g[13*MAXN*MAXN];int head[MAXN*MAXN+1];int work[MAXN*MAXN+1];int cnt;void addEdge(int u,int v,int w){    g[cnt].v = v;    g[cnt].w = w;    g[cnt].nxt = head[u];    head[u] = cnt;    ++ cnt;}int n;int a[MAXN+1][MAXN+1];bool cur[MAXN+1][MAXN+1];bool f = true,flag = true;int S,T;int sum,ans,flow;int dis[MAXN*MAXN+1];queue<int> q;int cal(int x,int y){    return (x-1)*n+y;    }void init(){    cnt = ans = flow = sum = 0;    memset(g,0,sizeof(g));    memset(head,-1,sizeof(head));    memset(cur,0,sizeof(cur));    f = true; flag = true;}bool bfs(){    memset(dis,-1,sizeof(dis));    while(!q.empty()) q.pop();    q.push(S);dis[S]=0;    while(!q.empty()){        int u = q.front();q.pop();        for(int i=head[u];i!=-1;i=g[i].nxt){            int v = g[i].v;            if((dis[v]==-1)&& g[i].w>0){                dis[v] = dis[u]+1;                q.push(v);            }        }    }    return (dis[T]!=-1);}int dfs(int u,int exp){    if(u==T) return exp;    int tmp = 0;    for(int &i=work[u];i!=-1;i=g[i].nxt){        int v = g[i].v;        if((dis[v]==dis[u]+1)&&(g[i].w>0)){            tmp = dfs(v,min(exp,g[i].w));            if(!tmp) continue;            g[i].w -= tmp;            g[i^1].w += tmp;            return tmp;        }    }    return 0;}int main(){    while(~scanf("%d",&n)){        S = 0; T = n*n+1;        init();        for(int i=1;i<=n;++i){            f=flag;            flag=!flag;            for(int j=1;j<=n;++j){                scanf("%d",&a[i][j]);                sum += a[i][j];                cur[i][j] = f;                f = !f;            }        }        for(int i=1;i<=n;++i){            for(int j=1;j<=n;++j){                int tmp = cal(i,j);                if(cur[i][j]){                    addEdge(S,tmp,a[i][j]);                    addEdge(tmp,S,0);                    if(i>1){                        addEdge(tmp,cal(i-1,j),inf);                        addEdge(cal(i-1,j),tmp,0);                    }                    if(i<n){                        addEdge(tmp,cal(i+1,j),inf);                        addEdge(cal(i+1,j),tmp,0);                    }                    if(j>1){                        addEdge(tmp,cal(i,j-1),inf);                        addEdge(cal(i,j-1),tmp,0);                    }                    if(j<n){                        addEdge(tmp,cal(i,j+1),inf);                        addEdge(cal(i,j+1),tmp,0);                    }                } else{                    addEdge(tmp,T,a[i][j]);                    addEdge(T,tmp,0);                }            }        }        while(bfs()){            memcpy(work,head,sizeof(head));            while(flow=dfs(S,inf)){                ans += flow;            }        }        printf("%d\n",sum-ans);            }}

进阶题Hdu 1569

还有Hdu 1569也是几乎完全一模一样的,只不过由n*n的方格改为
n*m罢了,没有什么区别,注意下细节就行了。

Hdu 3820则是一道值得一做的拓展题了。

题意

题意是给一个方格阵,每个方格可以放金蛋或者银蛋,放不同的蛋会分别拿到各自的分数,
但是,如果相邻的方格颜色一样,如果都是金色,就需要减掉G,否则减掉S,
现在求最大分数。

建图

其实这道题是方格取数的加强版,道理是一样的。
将所有方格黑白染色,
因为一个方格有金银两个状态,所以可以通过拆点的方法来做,
为了便于说明,定义黑 为黑色方格放金蛋的点,黑’为放银蛋,白为白色方格放金蛋,白’为银色
tips:拆点是网络流最基本的方法,必须要掌握。

为构造图使满足相同颜色的约束条件,所以黑 必须连接 白,而黑安排在左边,所以白安排在右边
所以源点连接黑,边权为该方格分数,也连接白’
黑‘->T,白->T
但为什么不能够源点连黑、黑’,白、白‘连汇点呢?解释如下

建图思路

首先肯定要黑连接黑’,权值为inf,使得黑和黑’只能选择一个,道理和前面的方格取数一样,这样的话即指黑和黑’之间存在边。
注意到一个细节,我们要转化成最大点权独立集,而是存在于二分图中的,如果黑、黑‘在二分图同一边,它们存在边,与二分图定义矛盾。

然后白’也连接白
此时黑连接白,权值为G,
因为我们知道求最大分数是求最大点权独立集=总点权-最小点权覆盖,
所以最小点权覆盖选择的边,就是最大点权独立集不选的边,
而最小点权覆盖=最小割,
所以最小割选择的边中不包括S、T的点就等于最小点权覆盖所需的点。
以一个图来做例子(题目中的第一个样例):
建图后如图所示:
这里写图片描述
经计算可以知道,最小割为以下红色的边:
这里写图片描述
那么也就是只需要选择1、4、3、1‘、4’、2就能实现完全覆盖,
所以选2‘、3构成最大点权独立集。
所以所以所有点中不包括最小割经过的边中不包括S、T的点的点,是最大点权独立集的点

因为最大点权独立集=点权和-最小割,
而相邻方格取同样颜色是要减去费用的,也就是可以体现在最小割上,所以可以通过构造边来实现,
最后,可以再次考虑一个S-u-v-T的路径,其中u,v代表相同颜色(金银)且相邻的方格。
那么这条路径由于割的性质不能够联通,
因此最小割只能经过S-u,v-T的其中一条边,或者经过u-v的边
这就意味着:
两个相邻的方格,如果要选同样颜色(金银),要不在两个当中只选一个,否则两个都选的话,就得多花费用(u-v边权)
这样就能够满足题意了,题目也就能够解决了。

代码

代码如下:

#include <iostream>#include <cstdio>#include <cstring>#include <queue>using namespace std;#define MAXN 50001#define inf 0x7fffffffstruct Edge{    int v,w,nxt;}g[MAXN];int head[MAXN];int cur[MAXN];int cnt;void addEdge(int u,int v,int w){    g[cnt] = (Edge){v,w,head[u]};    head[u] = cnt;    ++ cnt;}int Tc;int n,m,x,y;int S,T;queue<int> q;int ans,sum;int dis[MAXN];int a[101][101],b[101][101];int dx[5]={0,1,0,-1,0};int dy[5]={0,0,1,0,-1};void init(){    sum = ans = cnt = 0;    memset(g,0,sizeof(g));    memset(head,-1,sizeof(head));}inline int C(int i,int j){    return ((i-1)*m+j);}bool bfs(){    memset(dis,-1,sizeof(dis));    while(!q.empty()) q.pop();    q.push(S);dis[S]=0;    while(!q.empty()){        int u = q.front();q.pop();        for(int i=head[u];i!=-1;i=g[i].nxt){            int v = g[i].v;            if((dis[v]==-1)&&(g[i].w>0)){                dis[v] = dis[u]+1;                if(v==T) return true;                q.push(v);            }        }    }    return (dis[T]!=-1);}int dfs(int u,int exp){    if(u==T) return exp;    int tmp = 0,left = 0;    for(int &i=cur[u];i!=-1;i=g[i].nxt){        int v = g[i].v;        if((dis[v]==dis[u]+1)&&(g[i].w>0)){            tmp = dfs(v,min(exp,g[i].w));            if(!tmp){                dis[v]=-1;continue;            }            g[i].w -= tmp;            g[i^1].w += tmp;            left += tmp;            exp -= tmp;            if(!exp) break;        }    }    if(exp) dis[u]=-1;    return left;}int main(){    scanf("%d",&Tc);    for(int Cases=1;Cases<=Tc;++Cases){        init();        scanf("%d%d%d%d",&n,&m,&x,&y);        for(int i=1;i<=n;++i)            for(int j=1;j<=m;++j){                scanf("%d",&a[i][j]);sum += a[i][j];            }        for(int i=1;i<=n;++i)            for(int j=1;j<=m;++j){                scanf("%d",&b[i][j]);sum += b[i][j];            }        S = 0; T = (n*m)*2 +1;        for(int i=1;i<=n;++i){            for(int j=1;j<=m;++j){                if((i+j)%2==0){                    addEdge(S,C(i,j),a[i][j]);                    addEdge(C(i,j),S,0);                    addEdge(n*m+C(i,j),T,b[i][j]);                    addEdge(T,n*m+C(i,j),0);                    addEdge(C(i,j),n*m+C(i,j),inf);                    addEdge(n*m+C(i,j),C(i,j),0);                    for(int k=1;k<=4;++k){                        int sx = i+dx[k],sy = j+dy[k];                        if((sx>0)&&(sx<=n)&&(sy>0)&&(sy<=m)){                            addEdge(C(i,j),C(sx,sy),x);                            addEdge(C(sx,sy),C(i,j),0);                        }                    }                } else{                    addEdge(S,n*m+C(i,j),b[i][j]);                    addEdge(n*m+C(i,j),S,0);                    addEdge(C(i,j),T,a[i][j]);                    addEdge(T,C(i,j),0);                    addEdge(n*m+C(i,j),C(i,j),inf);                    addEdge(C(i,j),n*m+C(i,j),0);                    for(int k=1;k<=4;++k){                        int sx = i+dx[k],sy = j+dy[k];                        if((sx>0)&&(sx<=n)&&(sy>0)&&(sy<=m)){                            addEdge(n*m+C(i,j),n*m+C(sx,sy),y);                            addEdge(n*m+C(sx,sy),n*m+C(i,j),0);                        }                    }                }            }        }        while(bfs()){            memcpy(cur,head,sizeof(head));            ans += dfs(S,inf);        }        printf("Case %d: %d\n",Cases,sum-ans);    }}
原创粉丝点击