对最小割的一些理解
luogu2774 方格取数问题
S1
懵逼的我仍然沉浸在被zyc坑了一波的悲痛中,根本无心做题好伐……
就当锻炼思维了吧(去他MD!)
可能是刚才用脑过度了,现在一点思路都没有,这不科学啊,整理一下思路……
对于一个点,我们可以选,我们也可以不选。我们可以看做,如果选择这个点,那么与它相邻的点就没有选择权,如果不选择这个点,那么与它相邻的点就有选择权,但是仍然可以不选。
S2 如何抉择?
这里对于如何多选一还是很懵逼的。我们知道,大多数情况下网络流的题都是需要多选一的,可是一般都不能用流量为1的方式来选择。这个时候GAY哥提供了一种思路,就是求最小割,然后用总的来减去最小割就行了。我们知道,最大流虽然叫做最大流,但是真正决定它的是最小的边权,所以它具有最小的性质,所以就等于最小割。
所以在我们要最大结果的时候,可以把最大流想象成最小割的一种实现方式,构造出一个最小割的图,然后跑最大流就行了,因为它的结果和最小割是一样的。
对于这个题,我们就可以进行棋盘染色,染成黑白格,然后源点连接黑格,边权为点权,汇点连接白格,边权为点权,黑格和互相矛盾的白格之间连inf。这样看就是最小割套路题了,割掉一些黑格,割掉一些白格,使得源点和汇点不相连接,也就是说不存在既选一黑又选一白的情况了。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define rint register int
#define inv inline void
#define inb inline bool
#define ini inline int
#define big 1e9
using namespace std;
int n,m,k,cnt,head[100000],cur[100000],dis[100000],all,ans,tot,s=0,t=10001,a[101][101];
int dx[5]={0,0,0,1,-1},dy[5]={0,1,-1,0,0};
struct node
{
int from,to,dis,num,next;
}ljb[100000];
ini read()
{
char c;int r=0;
while (c<'0' || c>'9') c=getchar();
while (c>='0' && c<='9')
{
r=r*10+c-'0';
c=getchar();
}
return r;
}
inv add_edge(int x,int y,int z)
{
ljb[++cnt].from=x;
ljb[cnt].to=y;
ljb[cnt].dis=z;
ljb[cnt].num=cnt;
ljb[cnt].next=head[x];
head[x]=cnt;
}
inv add(int x,int y,int z)
{
add_edge(x,y,z);
add_edge(y,x,0);
}
queue<int> q;
inb bfs()
{
q.push(s);
memset(dis,128,sizeof(dis));
dis[s]=0;
while(!q.empty())
{
int x=q.front();
q.pop();
for (rint i=head[x];i!=-1;i=ljb[i].next)
{
int y=ljb[i].to;
if (dis[y]<0 && ljb[i].dis>0)
{
dis[y]=dis[x]+1;
q.push(y);
}
}
}
if (dis[t]>0) return 1;
return 0;
}
ini dfs(int x,int low)
{
if (x==t) return low;
for (rint& i=cur[x];i!=-1;i=ljb[i].next)
{
int y=ljb[i].to;
if (dis[y]==dis[x]+1 && ljb[i].dis>0)
{
int flow=dfs(y,min(low,ljb[i].dis));
if (flow)
{
ljb[i].dis-=flow;
ljb[ljb[i].num^1].dis+=flow;
return flow;
}
}
}
return 0;
}
inv md(int x,int y)
{
for (rint i=1;i<=4;i++)
{
int xx=x+dx[i];
int yy=y+dy[i];
if (xx>0 && xx<=m && yy>0 && yy<=n)
{
int zz=(xx-1)*n+yy;
add((x-1)*n+y,zz,big);
}
}
}
int main()
{
m=read();n=read();
for (rint i=s;i<=t;i++) head[i]=-1;cnt=-1;
for (rint i=1;i<=m;i++)
for (rint j=1;j<=n;j++)
{
a[i][j]=read();
all+=a[i][j];
int x=(i-1)*n+j;
if (i%2)
{
if (j%2) add(s,x,a[i][j]),md(i,j);
else add(x,t,a[i][j]);
}
else
{
if (j%2) add(x,t,a[i][j]);
else add(s,x,a[i][j]),md(i,j);
}
}
while (bfs())
{
for (rint i=s;i<=t;i++) cur[i]=head[i];
do
{
tot=dfs(s,big);
ans+=tot;
}
while (tot);
}
printf("%d",all-ans);
}
总结
- 先说一个细节:对于head和cnt的初始化一定不要忘了在所有add的前面。
- 总结一下矛盾问题:即我们上面说的多选一。对于存在矛盾,必须进行选择的点,在建图的时候,我们有一种思路,就是把两种立场分别标记为源点和汇点,然后选边站,两种选择必须二选一的连接起来,我们之前有过一道类似的题,叫做善意的投票(详见网络流学习笔记)。
- 也就是说,我们把一道网络流的题目转化为最小割,如果搞懂最小割割出来的集合是什么含义,就比较好理解了。以这道题为例,最小割割出来的集合,与源点相连接的就是要选的黑点和白点,以太空那道题为例,与源点相连的就是要选的实验和仪器。我们再看善意的投票,与源点相连的就是同意睡午觉的,与汇点相连的就是不同意的,所以我们要连双向边,因为可以为朋友反悔。
赞 赏
蒟蒻的邮箱:xmzl200201@126.com