[网络流24题] 方格取数问题 (最大权独立集---网络最小割)
734. [网络流24题] 方格取数问题 ★★☆ 输入文件:grid.in 输出文件:grid.out 简单对比 时间限制:1 s 内存限制:128 MB «问题描述: 在一个有m*n 个方格的棋盘中,每个方格中有一个正整数。现要从方格中取数,使任 意2 个数所在方格没有公共边,且取出的数的总和最大。试设计一个满足要求的取数算法。 «编程任务: 对于给定的方格棋盘,按照取数要求编程找出总和最大的数。 «数据输入: 由文件grid.in提供输入数据。文件第1 行有2 个正整数m和n,分别表示棋盘的行数 和列数。接下来的m行,每行有n个正整数,表示棋盘方格中的数。 «结果输出: 程序运行结束时,将取数的最大总和输出到文件grid.out中。 输入文件示例 输出文件示例 grid.in 3 3 1 2 3 3 2 3 2 3 1 grid.out 11 (1<=N,M<=30)
算法讨论: 图片原创:http://blog.csdn.net/water_glass/article/details/6853678
代码:
#include <cstdlib> #include <iostream> #include <cstring> #include <algorithm> #include <cstdio> #include <vector> using namespace std; const int N = 900 + 5; const int oo = 0x3f3f3f3f; struct Edge { int from, to, cap, flow; Edge(int u = 0, int v = 0, int cap = 0, int flow = 0) : from(u), to(v), cap(cap), flow(flow) {} }; struct Dinic { int nn, mm, s, t; int dis[N], cur[N], que[N * 10]; bool vis[N]; vector <Edge> edges; vector <int> G[N]; void add(int from, int to, int cap) { edges.push_back(Edge(from, to, cap, 0)); edges.push_back(Edge(to, from, 0, 0)); mm = edges.size(); G[from].push_back(mm - 2); G[to].push_back(mm - 1); } bool bfs() { int head = 1, tail = 1; memset(vis, false, sizeof vis); que[head] = s; dis[s] = 0; vis[s] = true; while(head <= tail) { int x = que[head]; for(int i = 0; i < (signed) G[x].size(); ++ i) { Edge &e = edges[G[x][i]]; if(!vis[e.to] && e.cap > e.flow) { vis[e.to] = true; dis[e.to] = dis[x] + 1; que[++ tail] = e.to; } } ++ head; } return vis[t]; } int dfs(int x, int a) { if(x == t || a == 0) return a; int flw = 0, f; for(int &i = cur[x]; i < (signed) G[x].size(); ++ i) { Edge &e = edges[G[x][i]]; if(dis[e.to] == dis[x] + 1 && (f = dfs(e.to, min(a, e.cap - e.flow))) > 0) { e.flow += f; edges[G[x][i] ^ 1].flow -= f; a -= f; flw += f; if(!a) break; } } return flw; } int maxflow(int s, int t) { this->s = s; this->t = t; int flw = 0; while(bfs()) { memset(cur, 0, sizeof cur); flw += dfs(s, oo); } return flw; } }net; int n, m, S, T, sum, cnt; int a[35][35], num[35][35]; int dx[]={0,1,-1,0}, dy[]={1,0,0,-1}; #define stone_e int main() { #ifndef stone_ freopen("grid.in", "r", stdin); freopen("grid.out", "w", stdout); #endif scanf("%d%d", &n, &m); S = 0; T = n * m + 1; net.nn = T; 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) num[i][j] = ++ cnt; for(int i = 1; i <= n; ++ i) for(int j = 1; j <= m; ++ j) if((i + j) & 1) net.add(S, num[i][j], a[i][j]); else net.add(num[i][j], T, a[i][j]); for(int i = 1; i <= n; ++ i) for(int j = 1; j <= m; ++ j) if((i + j) & 1) { for(int k = 0; k <= 3; ++ k) { int nx = dx[k] + i, ny = dy[k] + j; if(num[nx][ny]) net.add(num[i][j], num[nx][ny], oo); } } printf("%d\n", sum - net.maxflow(S, T)); #ifndef stone_ fclose(stdin); fclose(stdout); #endif return 0; }
这个题WA了一遍是因为二分图染色之后连边不对了,标号后不能直接连。
1 2 3 4
5 6 7 8 按我原来的算法,左边的点集选的是1 3 5 7,但是看他们的位置,并不是错开的。
一个保险正确的写法是每次选择(i + j) & 1 位置上的数字,这样就错开了。