您的位置:首页 > 其它

HDU 1569 最大点权独立集

2014-12-17 11:28 162 查看


最大点权独立集与最小点权覆盖是对偶问题,这里先介绍最小点权覆盖的解法。
最小点权覆盖问题是指,给出一张二分图,二分图的每个节点带有一个点权,要求从中选出若干节点,使得这些节点能够覆盖二分图中所有的边,并使得节点的权值和最小。
该类问题可用网络流最小割算法来解决。考虑最小割的性质,最小割能够将原图中所有的点划分为两个集合,这能够与最小点权覆盖问题中的点得选中与否对应;如果能够找到一种建模方式,满足在二分图中的每条边连接的两个点中,至少一个被选中,就能使网络流的最小割与二分图的最小点权覆盖相匹配。
于是考虑如下建模方式:建立源点s, 汇点t, 保留二分图中的所有节点xi,yi,从s到所有xi连边,权值为xi的权值;若二分图中xi到yi有边,在网络流图中从xi向yi连边,权值为无穷;从所有yi到t连边,权值为yi的权值。
这样,二分图中的每条边连接的两个点中,要么与s形成割边,要么与t形成割边(因为这两个点之间的边权为无穷),这就保证了两个点中,至少一个被选中,因此最小割就对应了一个最小点权的选点方案。故网络流图中的最小割就是原问题的最小点权。
而最大点权独立集问题中,需要满足在二分图中的每条边连接的两个点中,选择至多一个,最终是选择的点的点权和最大;换句话说,就是在二分图中的每条边连接的两个点中,至少选择一个,使得不选择的点的点权和最小。说到这里,大家应该能看出最大点权独立集合最小点权覆盖问题的联系了——最小点权覆盖的选点方案正好对应了最大点权独立集的选点方案。 于是最大点权独立集的点权和=所有点权和-最小点权覆盖的点权。 下面给出一道例题 HDU 1569 

方格取数(2)

Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 4807 Accepted Submission(s): 1515 [align=left]Problem Description[/align]给你一个m*n的格子的棋盘,每个格子里面有一个非负数。 从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取数所在的2个格子不能相邻,并且取出的数的和最大。 [align=left]Input[/align]包括多个测试实例,每个测试实例包括2整数m,n和m*n个非负数(m<=50,n<=50) [align=left]Output[/align]对于每个测试实例,输出可能取得的最大的和 [align=left]Sample Input[/align][code]3 3 75 15 21 75 15 28 34 70 5

[align=left]Sample Output[/align]
188

[align=left]Author[/align]ailyanlu 该题的实质就是一个最大点权独立集问题。首先可以看到一条性质,若将棋盘中的点抽象成图中的点,每个点向周围四个点连边,这样能够构成一张二分图。其次,若按以上方式建图,该二分图的最大点权独立集的点权和便是最佳方案。 下面是代码:#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

#define MAXN 3000
#define INF 1000000000

class Edge
{
public:
int u, v, w, next;
};
Edge edge[MAXN*8];

int fa[MAXN], head[MAXN], cur[MAXN], gap[MAXN], dis[MAXN], num;
/*
fa记录增广过程一个节点的父亲节点;
head是邻接表的表头;
cur当前弧优化数组;记录当前节点的第一条可能的允许弧;
gap是gap优化数组,gap[i]记录当前距离值为i的节点数;
dis储存节点的距离函数值
*/

void insert(int u, int v, int w)
{
edge[num].u = u;
edge[num].v = v;
edge[num].w = w;
edge[num].next = head[u];
head[u] = num;

num++;

edge[num].u = v;
edge[num].v = u;
edge[num].w = 0;
edge[num].next = head[v];
head[v] = num;

num++;
}
/*
最大流sap函数,传入参数s, t, n分别表示源节点,汇点,节点总数,函数返回网络流图中的最大流
复杂度O(E*V*V)

*/
int sap(int s, int t, int n)
{
memset(fa, -1, sizeof(fa));
memcpy(cur, head, sizeof(head));//初始时当前弧就是当前节点的第一条弧
memset(dis, 0, sizeof(dis));//距离函数初始为0
memset(gap, 0, sizeof(gap));

gap[0] = n;//初始时距离为0的节点有n个

int lowf = INF, top = s, flow = 0;//lowf是增广路上的最小流量,top是增广过程最前端的节点,flow待返回的流量

while(dis[s] < n)//当汇点不可达时,dis[s]的值会被更改为节点总数n
{
bool flag = 0;//标记通过top节点能否找到允许弧

for(int i = cur[top]; i != -1; i = edge[i].next)//从当前弧开始找允许弧
{
int v = edge[i].v;
if(dis[top] == dis[v]+1 && edge[i].w > 0)//找到允许弧
{
flag = 1;//更改标记
cur[top] = i;//更改当前节点的当前弧,下次搜索时从这条弧开始
lowf = min(lowf, edge[i].w);//更新增广路上的流量
fa[v] = top;//记录父节点
top = v;//更改top节点

if(top == t)//如果找到终点,说明找到一条增广路,更新总流量
{
flow += lowf;

while(top != s)//沿父节点回溯更新残余网络
{
top = fa[top];

edge[cur[top]].w -= lowf;
edge[cur[top]^1].w += lowf;

}

lowf = INF;//重置最小流量
}
break;
}

}

/*
如果没找到允许弧,撤退,更改当前top节点的距离值
*/
int mindis;//更改top节点的距离值
if(!flag)
{
mindis = n;//初始化为节点总数

for(int i = head[top]; i != -1; i = edge[i].next)
{
if(edge[i].w > 0 && dis[edge[i].v] < mindis)//如果top节点能从距离比当前节点的距离更小的节点转移来
{
mindis = dis[edge[i].v];//更新top节点的距离值
cur[top] = i;//修改top节点的当前弧
}
}
if((--gap[dis[top]]) == 0) break;//gap优化,如果节点距离值出现断层,必然找不到增广路,直接退出
gap[dis[top] = mindis+1]++;//更新top节点的距离值以及gap数组

if(top != s) top = fa[top];//top撤退到它的父节点
}

}
return flow;
}

int n, m;

int mat[55][55];
int dir[4][2] = { {1, 0}, {0, 1}, {0, -1}, {-1, 0} };

bool inbound(int row, int col)
{
if(row < 0 || row >= n || col < 0 || col >= m) return 0;
return 1;
}

int main()
{
//freopen("in.txt", "r", stdin);

while(scanf("%d%d", &n, &m) != -1)
{
int cnt = 1, src = 0, end = n*m+1, sum = 0;
num = 0;
memset(head, -1, sizeof(head));
for(int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
{
scanf("%d", &mat[i][j]);
sum += mat[i][j];
}
}

for(int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
{
if(((i+j)&1))
{
// cout << mat[i][j] << " <<<<<" << endl;

insert(i*m+j+1, end, mat[i][j]);
continue;
}

insert(src, i*m+j+1, mat[i][j]);
// cout << mat[i][j] << " >>>>>>>>> " << i*m+j+1 << endl;

for(int k = 0; k < 4; k++)
{
int r = i+dir[k][0], c = j + dir[k][1];

if(inbound(r, c))
{
insert(i*m+j+1, r*m+c+1, INF);
}

}
}
}

int ans = sap(src, end, n*m+2);
//cout << "ans " << ans << endl;

printf("%d\n", sum-ans);

}

return 0;
}
[/code]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: