最小割的应用--方格取数问题(HDU 1565,HDU 1569)

 

Description

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

解题思路

分析下题目的一些关键的特点:
首先此题最大特点就是一个点只和周围4个点有关联,简单的说就是可以通过一个奇偶染色构造二分图(为么么?),在假设如果每个的点权都为1,那此题就可以转换成最大独立集。那现在这是个什么问题呢?

其实这是一个二分图最大点权独立集问题,就是找出图中一些点,使得这些点之间没有边相连,这些点的权值之和最大。独立集与覆盖集是互补的,求最大点权独立集可以转化为求最小点权覆盖集(最小点权支配集)。最小点权覆盖集问题可以转化为最小割问题解决。

结论:
最大点权独立集 = 所有点权 - 最小点权覆盖集 = 所有点权 - 最小割集 = 所有点权 -网络最大流。

结论的简单说明:

对于一个网络,除去冗余点(不存在一条ST路径经过的点),每个顶点都在一个从S到T的路径上。
割的性质就是不存在从S到T的路径,简单割可以认为割边关联的非ST节点为割点,
而在二分图网络流模型中每个点必关联到一个割点(否则一定还有增广路,当前割不成立),
所以一个割集对应了一个覆盖集(支配集)。最小点权覆盖集就是最小简单割,求最小简单割
的建模方法就是把XY集合之间的变容量设为无穷大,此时的最小割就是最小简单割了

对此,我们对这个问题的模型基本构造完毕!

代码实现

HDU1565http://acm.hdu.edu.cn/showproblem.php?pid=1565的图比较小,我采用了邻接矩阵+EK算法求最大流:

//============================================================================
// Name        : 1001.cpp
// Author      : 
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;

const int inf=0x7fffffff;
const int MAXN=25;
int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};

struct Node{
	int x,y;
};

int val[MAXN][MAXN];
int grap[MAXN*MAXN][MAXN*MAXN];
int n,m;

inline bool Judge(Node a)
{
	return (a.x>=0&&a.x<n&&a.y>=1&&a.y<=n);
}
void Setup()
{
	memset(grap,0,sizeof(grap));
	for(int i=0;i<n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			//下面是二分这个图,根据奇偶性
			if((i+j)&1)
				grap[0][i*n+j]=val[i][j];//源点0到X集合的流
			else
				grap[i*n+j][n*n+1]=val[i][j];//Y集合到汇点的流
		}
	}
	Node next;
	for(int i=0;i<n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			for(int k=0;k<4;k++)
			{
				next.x=i+dir[k][0];
				next.y=j+dir[k][1];
				if(Judge(next))
				{
					//构造X/Y集合之间的流,注意流要是一个方向的
					//否则最小割不会是最小点权覆盖集
					if((i+j)&1)
						grap[i*n+j][next.x*n+next.y]=inf;
				}
			}
		}
	}
}

int Edmonds_Karp(int s,int t)
{
	int p,q,que[MAXN*MAXN],u,v,pre[MAXN*MAXN],flow=0,aug;
	while(true)
	{
		memset(pre,-1,sizeof(pre));
		for(que[p=q=0]=s;p<=q;p++)
		{
			u=que[p];
			for(v=0;v<m && pre[t]<0;v++)
			{
				if(grap[u][v]>0 && pre[v]<0)
					pre[v]=u,que[++q]=v;
			}
			if(pre[t]>=0) break;
		}
		if(pre[t]<0) break;
		aug=inf;
		for(u=pre[v=t];v!=s;v=u,u=pre[u])
			if(grap[u][v]<aug)
				aug=grap[u][v];
		for(u=pre[v=t];v!=s;v=u,u=pre[u])
		{
			grap[u][v]-=aug,grap[v][u]+=aug;
		}
		flow+=aug;
	}
	return flow;
}

int main() {
	while(scanf("%d",&n)!=EOF)
	{
		int sum=0;
		for(int i=0;i<n;i++)
		{
			for(int j=1;j<=n;j++)
			{
				scanf("%d",&val[i][j]);
				sum+=val[i][j];
			}
		}
		m=n*n+2;//m为顶点数
		Setup();
		int flg=Edmonds_Karp(0,n*n+1);
		printf("%d\n",sum-flg);
	}
	return 0;
}


HDU1569http://acm.hdu.edu.cn/showproblem.php?pid=1569数据比较大,用邻接矩阵会超内存!所以采用边集+Dinic算法求最大流

//============================================================================
// Name        : 1001.cpp
// Author      : 
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
const int inf = 0x7fffffff;
const int MAXN = 55;
int dir[4][2] = { { 0, 1 }, { 0, -1 }, { 1, 0 }, { -1, 0 } };

struct Node {
	int x, y;
};

struct edge {
	int x, y, nxt, c;
} bf[1001000];
int ne, head[2505], cur[2505], ps[2505], dep[2505];
void addedge(int x, int y, int c) {
	bf[ne].x = x;
	bf[ne].y = y;
	bf[ne].c = c;
	bf[ne].nxt = head[x];
	head[x] = ne++;
	bf[ne].x = y;
	bf[ne].y = x;
	bf[ne].c = 0;
	bf[ne].nxt = head[y];
	head[y] = ne++;
}

int maxflow(int nd, int s, int t) {
	int tr, res = 0;
	int i, j, k, f, r, top;
	while (true) {
		memset(dep, -1, sizeof(dep));
		for (f = dep[ps[0] = s] = 0, r = 1; f != r;) {
			for (i = ps[f++], j = head[i]; j; j = bf[j].nxt) {
				if (bf[j].c && -1 == dep[k = bf[j].y]) {
					dep[k] = dep[i] + 1;
					ps[r++] = k;
					if (k == t) {
						f = r;
						break;
					}
				}
			}
		}
		if (-1 == dep[t])
			break;
		memcpy(cur, head, nd * sizeof(int));
		for (i = s, top = 0;;) {
			if (i == t) {
				for (k = 0, tr = inf; k < top; ++k)
					if (bf[ps[k]].c < tr)
						tr = bf[ps[f = k]].c;
				for (k = 0; k < top; ++k)
					bf[ps[k]].c -= tr, bf[ps[k] ^ 1].c += tr;
				res += tr;
				i = bf[ps[top = f]].x;
			}
			for (j = cur[i]; cur[i]; j = cur[i] = bf[cur[i]].nxt)
				if (bf[j].c && dep[i] + 1 == dep[bf[j].y])
					break;
			if (cur[i]) {
				ps[top++] = cur[i];
				i = bf[cur[i]].y;
			} else {
				if (0 == top)
					break;
				dep[i] = -1;
				i = bf[ps[--top]].x;
			}
		}
	}
	return res;
}

int val[MAXN][MAXN];
//int grap[MAXN * MAXN][MAXN * MAXN];
int n, m, nodes;

inline bool Judge(Node a) {
	return (a.x >= 0 && a.x < n && a.y >= 1 && a.y <= m);
}

void Setup() {
	ne=2;
	memset(head,0,sizeof(head));
	memset(bf, 0, sizeof(bf));
	for (int i = 0; i < n; i++) {
		for (int j = 1; j <= m; j++) {
			if ((i + j) & 1) {
				addedge(0,i*m+j,val[i][j]);
//				grap[0][i * m + j] = val[i][j];
			} else {
				addedge(i*m+j,n*m+1,val[i][j]);
//				grap[i * m + j][n * m + 1] = val[i][j];
			}
		}
	}
	Node next;
	for (int i = 0; i < n; i++) {
		for (int j = 1; j <= m; j++) {
			for (int k = 0; k < 4; k++) {
				next.x = i + dir[k][0];
				next.y = j + dir[k][1];
				if (Judge(next)) {
					if ((i + j) & 1)
					{
						addedge(i*m+j,next.x*m+next.y,inf);
//						grap[i * m + j][next.x * m + next.y] = inf;
					}
				}
			}
		}
	}
}

int main() {
	while (scanf("%d%d", &n, &m) != EOF) {
		int sum = 0;
		for (int i = 0; i < n; i++) {
			for (int j = 1; j <= m; j++) {
				scanf("%d", &val[i][j]);
				sum += val[i][j];
			}
		}
		nodes = n * m + 2;
		Setup();
		int flg = maxflow(nodes,0,n*m+1);
//				printf("maxflow=%d\n",flg);
		printf("%d\n", sum - flg);
	}
	return 0;
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值