\(\quad\) 在 上一篇博客 中简要地讲解了最大权闭合子图。在这一篇博客里,我希望能够写一下最大权闭合子图的一些拓展应用。
\(\quad\) 以 P2774 方格取数问题 为例。
有一个 \(m\) 行 \(n\) 列的方格图,每个方格中都有一个正整数。现要从方格中取数,使任意两个数所在方格没有公共边,且取出的数的总和最大,请求出最大的和。
\(\quad\) 这一道题有收益元素,却没有代价元素;关系限制也与一般的最大权闭合子图不一样。最大权闭合子图的关系限制的通式是若 \(i\),则一定 \(j\),而这道题的形式却是若 \(i\),则一定不 \(j\)。然而,把棋盘像国际象棋那样染色,我们却可以发现,若取一个白格,则一定不取它周围的黑格,这样就得到了最大权闭合子图的三要素:
- 收益元素:取白格
- 代价元素:不取黑格
- 限制关系:若(取白格),则一定(不取周围的黑格)。
#include <cstdio>
#include <cstring>
#define index ((i - 1) * n + j)
inline int read(void){
int res = 0; char ch = std::getchar();
while(ch < '0' || ch > '9')
ch = std::getchar();
while(ch >= '0' && ch <= '9')
res = res * 10 + ch - 48, ch = std::getchar();
return res;
}
const int MAXN = 1e2 + 19, INF = 0x3f3f3f3f;
struct Queue{
int q[MAXN * MAXN << 3], head, tail;
int& operator[](const int& __x){
return q[__x];
}
};
struct Edge{
int to, next, c;
}edge[MAXN * MAXN << 3];
int cnt = -1, head[MAXN * MAXN];
inline void add(int from, int to, int c){
edge[++cnt].to = to;
edge[cnt].c = c;
edge[cnt].next = head[from];
head[from] = cnt;
}
int m, n, sq[MAXN][MAXN], ans;
int s = 0, t;
int dep[MAXN * MAXN];
int bfs(void){
static Queue q; q[q.head = q.tail = 0] = s;
std::memset(dep, 0, sizeof(dep)); dep[s] = 1;
while(q.head <= q.tail){
int node = q[q.head++];
for(int i = head[node]; i != -1; i = edge[i].next)
if(!dep[edge[i].to] && edge[i].c)
dep[edge[i].to] = dep[node] + 1, q[++q.tail] = edge[i].to;
}
return dep[t];
}
inline int min(const int& a, const int& b){
return a < b ? a : b;
}
int dfs(int node, int flow){
if(node == t || !flow)
return flow;
int stream = 0, f;
for(int i = head[node]; i != -1; i = edge[i].next)
if(dep[edge[i].to] == dep[node] + 1 && (f = dfs(edge[i].to, min(flow, edge[i].c)))){
flow -= f, stream += f;
edge[i].c -= f, edge[i ^ 1].c += f;
if(!flow)
break;
}
return stream;
}
int dinic(void){
int flow = 0;
while(bfs())
flow += dfs(s, INF);
return flow;
}
int main(){
std::memset(head, -1, sizeof(head));
m = read(), n = read();
t = m * n + 1;
for(int i = 1; i <= m; ++i)
for(int j = 1; j <= n; ++j)
ans += sq[i][j] = read();
for(int i = 1; i <= m; ++i)
for(int j = 1; j <= n; ++j)
if((i + j) & 1){
add(s, index, sq[i][j]), add(index, s, 0);
if(i > 1)
add(index, index - n, INF), add(index - n, index, 0);
if(i < m)
add(index, index + n, INF), add(index + n, index, 0);
if(j > 1)
add(index, index - 1, INF), add(index - 1, index, 0);
if(j < n)
add(index, index + 1, INF), add(index + 1, index, 0);
}
else
add(index, t, sq[i][j]), add(t, index, 0);
ans -= dinic();
printf("%d\n", ans);
return 0;
}
\(\quad\) 通过把取黑格转换为不取黑格,我们巧妙地构造了一个最大权闭合子图。
\(\quad\) 再看另一道题 P2598 [ZJOI2009]狼和羊的故事。
在 \(n\times m\) 的棋盘上有三种格子:狼,羊,空。请沿着格子的边缘建立最少的篱笆,使得任意两个羊和狼既没有公共边也无法通过空地相连。
\(\quad\) 找到关系限制:若 \(i\) 是一个狼领地的边,\(j\) 是一个羊领地的边,且 \(i\) 与 \(j\) 经过若干(可以是零)块空地相连,那么若 \(i\) 不建篱笆,\(j\) 就一定建篱笆。收益元素是:\(i\) 不建篱笆,代价元素是:\(j\) 建篱笆。这样就转换为最大权闭合子图了。
\(\quad\) 其实以上两题还有别的理解方式,我只是希望能用一个形式化的模板概括它们。