若有疏漏,敬请指出不足之处,谢谢!!!
最近刷了网络流的不少题目,,,所以决定总结一下关于方格取数问题的基本做法。
大概的题意是:
给你一个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被选中,
也就是满足所有边都覆盖。
当是最小割时,点权覆盖也为最小。
所以最小割=最小点权覆盖,
同时又最大流最小割定理可知,最大流=最小割,所以这类题目可以跑一下网络流最大流来完成。
Hdu 1565代码如下:
#include
#include
#include
#include
#include
using namespace std;
#define MAXN 21
#define inf 0x7fffffff
struct 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(i1,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(j1),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也是几乎完全一模一样的,只不过由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
#include
#include
#include
using namespace std;
#define MAXN 50001
#define inf 0x7fffffff
struct 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);
}
}