网络流24题(九)
网络流24题(九)
九、方格取数问题
题目描述
有一个 \(m\) 行 \(n\) 列的方格图,每个方格中都有一个正整数。现要从方格中取数,使任意两个数所在方格没有公共边,且取出的数的总和最大,请求出最大的和。
输入格式
第一行是两个用空格隔开的整数,分别代表方格图的行数 \(m\) 和列数 \(n\)。
第 \(2\) 到第 \((m + 1)\) 行,每行 \(n\) 个整数,第 \((i + 1)\) 行的第 \(j\) 个整数代表方格图第 \(i\) 行第 \(j\) 列的的方格中的数字 \(a_{i, j}\)。
输出格式
输出一行一个整数,代表和最大是多少。
PS:这个傻逼题目\(m\)行\(n\)列,我的代码是\(n\)行\(m\)列的
题解
模型:
二分图最大权独立集,这个问题等价于求二分图最小权覆盖集的补集,然后后者可以建图用最小割求。
下面解释一下,最小权覆盖集和最大权独立集。
覆盖集:在一张图上选若干个点,这些点满足他们的邻边覆盖整张图,那么这些点就是覆盖集,最小覆盖集就是点数最少的覆盖集合,最小权覆盖集就是如果我们给上点权,我们求出的权值和最小的覆盖集合。
独立集:在一张图上选若干个点,这些点满足两两之间没有边相连,相互独立,我们称这个点集合为独立集,当这个集合点数最多的时候就是最大独立集,如果给上点权,权值和最大的就是最大独立集。
覆盖集与独立集:如果\(V\)是一个覆盖集,那么满足所有边都有一个点至少在\(V\)集合中,那么显然,剩下的所有点都是不相连的,所以\(V\)的补集\(V'\)就是独立集,同样的最小权覆盖集的补集就是最大全独立集。
所以我们知道,如果我们要求最大权独立集可以考虑先求最小权覆盖集。
那么如何求最小权覆盖集呢?
\(Amber\)胡伯涛在论文《最小割模型在信息学竞赛中的应用》给了一个十分清晰的思考过程:
回顾与此模型相关的模型。简化权的条件后,可以借鉴的是二分图匹配的最大流解法。
它加入了额外的源 \(s\)和汇 \(t\) ,将匹配以一条条\(s-u-v-t\)形式的流路径“串联”起来。匹配的限制是在点上,恰当地利用了流的容量限制。而点覆盖集的限制在边上,最小割是最大流的对偶问题,对偶往往是将问题的性质从点转边,从边转点。可以尝试着转化到最小割模型。
在这个思路的启发下,我们开始考虑最小割和覆盖集关系。
建图的时候参考二分图匹配时的建图,建立一个源点\(s\)和一个汇点\(t\),不难发现二分图中的每一条边\((u,v)\)都对应一条\(s-u-v-t\)的路径,在这样的一条路径当中势必会有一条边存在割中。
根据割的定义,割会做到这样一件事情:取走割中的所有边,所有路径不连通。
所有路径?再联系之前说到一句每一条边\((u,v)\)对应一条\(s-u-v-t\)的路径,这样的话我们也找到了所有的边。这与覆盖集想要做的事情一模一样。
现在要求的是覆盖集,我们的注意力应该要放在点上,假设选择了\(u-v\)这样的边放在割中不能体现我们对点的选取,所以我们人为的让\(u-v\)这样的边容量限制为\(inf\),这样我们边的选取就是只会在\(s-u\)和\(v-t\)之间进行。
这样我们就发现,覆盖集与割是一一对应的,所以最小覆盖集就是最小割。
最后就是给上权值,稍加思考就发现其实和之前并没有什么不同,只是建图时边的容量略加修改即可。
求最大独立集即是求最小覆盖集的补集。
建图与实现:
这张图是二分图:考虑黑白染色,黑色之外必定为白色,不难看出黑色与白色分别为二分图的两部分,如果在棋盘中相邻则连边,边得容量为\(inf\)。
建立一个源点\(s\)一个汇点\(t\),源点\(s\)向黑色连边,边的容量为格子的得分,白色向汇点\(s\)连边,边的容量为格子的得分。
整理下思绪:
在二分图下:
最小覆盖集 = 最小割 = 最大流 = 最大匹配 = \(n\)-最大独立集
代码
#include <iostream>
#include <queue>
#include <cstring>
#define ll long long
const ll N = 5e3+50,M = 5e4+50;
const ll inf = 0x3f3f3f3f;
using namespace std;
ll head[N] = {0}, cnt = 1;
struct Edge{
ll to,w,nxt;
}edge[M*2];
void add(ll u,ll v,ll w){
edge[++cnt] = {v,w,head[u]};
head[u] = cnt;
}
void add2(ll u,ll v,ll w){
//cout<<"u:"<<u<<"v:"<<v<<endl;
add(u,v,w);
add(v,u,0);
}
ll s,t,lv[N],cur[N];
bool bfs(){
memset(lv,-1,sizeof lv);
lv[s] = 0;
memcpy(cur,head,sizeof head);
queue<ll> q;q.push(s);
while(!q.empty()){
ll p = q.front();q.pop();
for(ll eg = head[p];eg;eg = edge[eg].nxt){
ll to = edge[eg].to,vol = edge[eg].w;
//cout<<"from: "<<p<<" to:"<<to<<" vol: "<<vol<<endl;
if(vol > 0 && lv[to] == -1){
lv[to] = lv[p]+1;
q.push(to);
}
}
}
return lv[t] != -1;
}
ll dfs(ll p = s,ll flow = inf){
if(p == t) return flow;
ll rmn = flow;
for(ll &eg = cur[p];eg;eg = edge[eg].nxt){
if(!rmn) break;
ll to = edge[eg].to,vol = edge[eg].w;
if(vol > 0 && lv[to] == lv[p]+1){
ll c = dfs(to,min(vol,rmn));
rmn -= c;
edge[eg].w -= c;
edge[eg^1].w += c;
}
}
return flow-rmn;
}
ll dinic(){
ll ans = 0;
while(bfs()) ans += dfs();
return ans;
}
ll n,m,chess[N][N];
int main() {
ll sum = 0;
cin>>n>>m;
s = 0,t = n*m+1;
ll x = 0;
for(ll i = 1;i <= n;i++) {
for (ll j = 1; j <= m; j++) {
x++;
cin >> chess[i][j];
sum += chess[i][j];
if((i&1) == (j&1))add2(s, x, chess[i][j]);
else add2(x,t,chess[i][j]);
}
}
x = 0;
for(ll i = 1;i <= n;i++){
for(ll j = 1;j <= m;j++) {
x++;
if((i&1) != (j&1)) continue;
if (i != 1)add2(x, x - m, inf);
if (i != n)add2(x, x + m, inf);
if (j != 1)add2(x, x - 1, inf);
if (j != m)add2(x, x + 1, inf);
}
}
cout<<sum-dinic()<<endl;
return 0;
}