最大点权独立集与最小点权覆盖是对偶问题,这里先介绍最小点权覆盖的解法。最小点权覆盖问题是指,给出一张二分图,二分图的每个节点带有一个点权,要求从中选出若干节点,使得这些节点能够覆盖二分图中所有的边,并使得节点的权值和最小。该类问题可用网络流最小割算法来解决。考虑最小割的性质,最小割能够将原图中所有的点划分为两个集合,这能够与最小点权覆盖问题中的点得选中与否对应;如果能够找到一种建模方式,满足在二分图中的每条边连接的两个点中,至少一个被选中,就能使网络流的最小割与二分图的最小点权覆盖相匹配。于是考虑如下建模方式:建立源点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): 1515Problem Description给你一个m*n的格子的棋盘,每个格子里面有一个非负数。 从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取数所在的2个格子不能相邻,并且取出的数的和最大。Input包括多个测试实例,每个测试实例包括2整数m,n和m*n个非负数(m<=50,n<=50)Output对于每个测试实例,输出可能取得的最大的和Sample Input3 3 75 15 21 75 15 28 34 70 5
Sample Output188
Authorailyanlu该题的实质就是一个最大点权独立集问题。首先可以看到一条性质,若将棋盘中的点抽象成图中的点,每个点向周围四个点连边,这样能够构成一张二分图。其次,若按以上方式建图,该二分图的最大点权独立集的点权和便是最佳方案。下面是代码:#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; }