「网络流 24 题」方格取数 最大点权独立集

在一个有 $ m \times n $ 个方格的棋盘中,每个方格中有一个正整数。
现要从方格中取数,使任意 $ 2 $ 个数所在方格没有公共边,且取出的数的总和最大。


题目链接


概念

点覆盖集:无向图G的一个点集,使得该图中所有边都至少有一个端点在该集合内。

点独立集:无向图G的一个点集,使得任两个在该集合中的点在原图中都不相邻。

最小点权覆盖集:点覆盖集中权值和最小的。

最大点权独立集:点独立集中权值和最大的。

最小点权覆盖集=最大流=最小割

最大点权独立集=权值和-最小点权覆盖集

题解

  • 问题求的是最大点权独立集。
  • 最大点权独立集=权值和-最小点权覆盖集,可以理解为只有取出了最小点权覆盖集使得任意一条边都被选取,剩下的点即为最大点权独立集。
  • 求最大流即可
  • 又写错了点转化为值,正确:$(i-1)m+j$

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 10100;
const int INF=1e9+7;
const int xx[14]={-1, 1,0,0};
const int yx[14]={0,0,1,-1};
int n, m , mp[maxn][maxn];
ll sum;
int id(int i, int j){
return (i-1)*m+j;
}


// Dinic(最大流算法)

struct Edge
{
int from, to, cap, flow;
Edge(int u,int v,int c,int f):from(u), to(v), cap(c), flow(f) {}
};


struct Dinic
{
int n, m, s, t; // 节点数,边数(包括反向弧),源点编号和汇点编号
vector<Edge> edges; // 边数。edges[e]和edges[e^1]互为反向弧
vector<int> G[maxn]; // 邻接表,G[i][j]表示节点i的第j条边在e数组的序号
bool vis[maxn]; // BFS使用
int d[maxn]; // 从起点到i的距离
int cur[maxn]; // 当前弧下标

void init()
{
for(int i=0; i<=n; i++)
G[i].clear();
edges.clear();
}

void Addedge(int from, int to, int cap)
{
edges.push_back(Edge(from, to, cap, 0));
edges.push_back(Edge(to, from, 0, 0));
m = edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}

bool BFS()
{
memset(vis,0,sizeof(vis));
queue<int>Q;
Q.push(s);
d[s]=0;
vis[s]=1;
while(!Q.empty())
{
int x=Q.front();
Q.pop();
for(int i=0; i<G[x].size(); i++)
{
Edge& e=edges[G[x][i]];
if(!vis[e.to] && e.cap>e.flow) //只考虑残量网络中的弧
{
vis[e.to]=1;
d[e.to]=d[x]+1;
Q.push(e.to);
}
}
}
return vis[t];
}

int DFS(int x,int a)
{
if(x==t || a==0) return a;
int flow=0, f;
for(int& i=cur[x]; i<G[x].size(); i++) // 从上次考虑的弧
{
Edge& e=edges[G[x][i]];
if(d[x]+1 == d[e.to] && (f=DFS(e.to, min(a, e.cap-e.flow)))>0)
{
e.flow+=f;
edges[G[x][i]^1].flow-=f;
flow+=f;
a-=f;
if(a==0) break;
}
}
return flow;
}

int Maxflow(int s,int t)
{
this->s=s; this->t=t;
int flow=0;
while(BFS())
{
memset(cur, 0, sizeof(cur));
flow += DFS(s,INF);
}
return flow;
}
};


Dinic now;
int main()
{
while(scanf("%d%d", &n, &m)!=EOF)
{
sum=0;
now.n=50*50+30;
now.init();
int st=0,ed=n*m+1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d", &mp[i][j]);
sum+=mp[i][j];
if((i+j)%2)
{
int nowid=id(i, j);
now.Addedge(st,nowid,mp[i][j]);
for(int k=0;k<4;k++)
{
int x=i+xx[k];
int y=j+yx[k];
if(x>=1 && x<=n &&y>=1&&y<=m)
{
int toid=id(x,y);
now.Addedge(nowid, toid, INF);
}
}
}
else
{
int nowid=id(i, j);
now.Addedge(nowid,ed,mp[i][j]);
}
}
}
int ans=sum-now.Maxflow(st,ed);
printf("%d\n", ans);
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 10100;
const int INF=1e9+7;
const int xx[14]={-1, 1,0,0};
const int yx[14]={0,0,1,-1};
int n, m , mp[maxn][maxn];
ll sum;
int id(int i, int j){
return (i-1)*m+j;
}


// Dinic(最大流算法)

struct Edge
{
int from, to, cap, flow;
Edge(int u,int v,int c,int f):from(u), to(v), cap(c), flow(f) {}
};


struct Dinic
{
int n, m, s, t; // 节点数,边数(包括反向弧),源点编号和汇点编号
vector<Edge> edges; // 边数。edges[e]和edges[e^1]互为反向弧
vector<int> G[maxn]; // 邻接表,G[i][j]表示节点i的第j条边在e数组的序号
bool vis[maxn]; // BFS使用
int d[maxn]; // 从起点到i的距离
int cur[maxn]; // 当前弧下标

void init()
{
for(int i=0; i<=n; i++)
G[i].clear();
edges.clear();
}

void Addedge(int from, int to, int cap)
{
edges.push_back(Edge(from, to, cap, 0));
edges.push_back(Edge(to, from, 0, 0));
m = edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}

bool BFS()
{
memset(vis,0,sizeof(vis));
queue<int>Q;
Q.push(s);
d[s]=0;
vis[s]=1;
while(!Q.empty())
{
int x=Q.front();
Q.pop();
for(int i=0; i<G[x].size(); i++)
{
Edge& e=edges[G[x][i]];
if(!vis[e.to] && e.cap>e.flow) //只考虑残量网络中的弧
{
vis[e.to]=1;
d[e.to]=d[x]+1;
Q.push(e.to);
}
}
}
return vis[t];
}

int DFS(int x,int a)
{
if(x==t || a==0) return a;
int flow=0, f;
for(int& i=cur[x]; i<G[x].size(); i++) // 从上次考虑的弧
{
Edge& e=edges[G[x][i]];
if(d[x]+1 == d[e.to] && (f=DFS(e.to, min(a, e.cap-e.flow)))>0)
{
e.flow+=f;
edges[G[x][i]^1].flow-=f;
flow+=f;
a-=f;
if(a==0) break;
}
}
return flow;
}

int Maxflow(int s,int t)
{
this->s=s; this->t=t;
int flow=0;
while(BFS())
{
memset(cur, 0, sizeof(cur));
flow += DFS(s,INF);
}
return flow;
}
};


Dinic now;
int main()
{
while(scanf("%d%d", &n, &m)!=EOF)
{
sum=0;
now.n=50*50+30;
now.init();
int st=0,ed=n*m+1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d", &mp[i][j]);
sum+=mp[i][j];
if((i+j)%2)
{
int nowid=id(i, j);
now.Addedge(st,nowid,mp[i][j]);
for(int k=0;k<4;k++)
{
int x=i+xx[k];
int y=j+yx[k];
if(x>=1 && x<=n &&y>=1&&y<=m)
{
int toid=id(x,y);
now.Addedge(nowid, toid, INF);
}
}
}
else
{
int nowid=id(i, j);
now.Addedge(nowid,ed,mp[i][j]);
}
}
}
int ans=sum-now.Maxflow(st,ed);
printf("%d\n", ans);
}
return 0;
}