BZOJ 1001: [BeiJing2006]狼抓兔子【最大流/SPFA+最小割,多解】

时间:2021-08-24 09:12:32

1001: [BeiJing2006]狼抓兔子

Time Limit: 15 Sec  Memory Limit: 162 MB
Submit: 23822  Solved: 6012
[Submit][Status][Discuss]

Description

现在小朋友们最喜欢的"喜羊羊与灰太狼",话说灰太狼抓羊不到,但抓兔子还是比较在行的,
而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对下面这样一个网格的地形:

BZOJ 1001: [BeiJing2006]狼抓兔子【最大流/SPFA+最小割,多解】

左上角点为(1,1),右下角点为(N,M)(上图中N=4,M=5).有以下三种类型的道路 
1:(x,y)<==>(x+1,y) 
2:(x,y)<==>(x,y+1) 
3:(x,y)<==>(x+1,y+1) 
道路上的权值表示这条路上最多能够通过的兔子数,道路是无向的. 左上角和右下角为兔子的两个窝,
开始时所有的兔子都聚集在左上角(1,1)的窝里,现在它们要跑到右下解(N,M)的窝中去,狼王开始伏击
这些兔子.当然为了保险起见,如果一条道路上最多通过的兔子数为K,狼王需要安排同样数量的K只狼,
才能完全*这条道路,你需要帮助狼王安排一个伏击方案,使得在将兔子一网打尽的前提下,参与的
狼的数量要最小。因为狼还要去找喜羊羊麻烦.

Input

第一行为N,M.表示网格的大小,N,M均小于等于1000.
接下来分三部分
第一部分共N行,每行M-1个数,表示横向道路的权值. 
第二部分共N-1行,每行M个数,表示纵向道路的权值. 
第三部分共N-1行,每行M-1个数,表示斜向道路的权值. 
输入文件保证不超过10M

Output

输出一个整数,表示参与伏击的狼的最小数量.

Sample Input

3 4
5 6 4
4 3 1
7 5 3
5 6 7 8
8 7 6 5
5 5 5
6 6 6

Sample Output

14

HINT

2015.4.16新加数据一组,可能会卡掉从前可以过的程序。

Source

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1001

分析:最大流写法如下:

 #include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
inline int read()
{
int x=,f=;
char ch=getchar();
while(ch<''||ch>'')
{
if(ch=='-')
f=-;
ch=getchar();
}
while(ch>=''&&ch<='')
{
x=x*+ch-'';
ch=getchar();
}
return x*f;
}
inline void write(int x)
{
if(x<)
{
putchar('-');
x=-x;
}
if(x>)
{
write(x/);
}
putchar(x%+'');
}
int ne;
const int N=;
struct data
{
int to,next,v;
}e[N<<];
int head[N];
int h[N],q[N],ans;
inline void update(int u,int v,int w)
{
ne++;
e[ne].to=v;
e[ne].v=w;
e[ne].next=head[u];
head[u]=ne;
}
inline bool BFS()
{
int now,i;
memset(h,-,sizeof(h));
int t=,w=;
q[t]=;
h[]=;
while(t<w)
{
now=q[t];
t++;
i=head[now];
while(i)
{
if(e[i].v&&h[e[i].to]<)
{
q[w++]=e[i].to;
h[e[i].to]=h[now]+;
}
i=e[i].next;
}
}
if(h[n*m]==-)
return false;
return true;
}
inline int DFS(int x,int f)
{
if(x==n*m)
return f;
int i=head[x];
int w,used=;
while(i)
{
if(e[i].v&&h[e[i].to]==h[x]+)
{
w=f-used;
w=DFS(e[i].to,min(w,e[i].v));
e[i].v-=w;
e[i+].v+=w;
used+=w;
if(used==f)
return f;
}
i=e[i].next;
}
if(!used)
h[x]=-;
return used;
}
inline void dinic()
{
while(BFS())
{
ans+=DFS(,0x7f7f7f7f);
}
}
int main()
{
n=read();
m=read();
int x;
for(int i=;i<=n;i++)
{
for(int j=;j<m;j++)
{
x=read();
update(m*(i-)+j,m*(i-)+j+,x);
update(m*(i-)+j+,m*(i-)+j,x);
}
}
for(int i=;i<n;i++)
{
for(int j=;j<=m;j++)
{
x=read();
update(m*(i-)+j,m*(i)+j,x);
update(m*(i)+j,m*(i-)+j,x);
}
}
for(int i=;i<n;i++)
{
for(int j=;j<m;j++)
{
x=read();
update(m*(i-)+j,m*(i)+j+,x);
update(m*(i)+j+,m*(i-)+j,x);
}
}
dinic();
write(ans);
return ;
}

分析:最小割,上网看了别人的博客,学习到了s-t平面图的最小割的解法,把原图中的面看作点,起点和终点都等同于最外面的那一个面,原图中一条边权值为w,新图中就等同于此边在平面图中分割开的两个面(即新图中两个点)连一条边,权值为w。建模完成后,新图中的起点和终点的一条路径就穿插过原图的一些边,即一条路径等于原图中的一个割,所以最小割就等于新图的最短路径长度。确实很厉害。

推荐文章:浅析最大最小定理在信息学竞赛中的应用》--周冬

下面给出SPFA+最小割代码:

 #include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read()
{
int x=,f=;
char ch=getchar();
while(ch<''||ch>'')
{
if(ch=='-')
f=-;
ch=getchar();
}
while(ch>=''&&ch<='')
{
x=x*+ch-'';
ch=getchar();
}
return x*f;
}
inline void write(int x)
{
if(x<)
{
putchar('-');
x=-x;
}
if(x>)
{
write(x/);
}
putchar(x%+'');
}
#define M 2000001
int n,m,nm;
struct data
{
int to,next,v;
}e[*M];
int dis[M],q[M],head[M];
bool flag[M];
int ne;
inline void update(int u,int v,int w)
{
ne++;
e[ne].to=v;
e[ne].v=w;
e[ne].next=head[u];
head[u]=ne;
ne++;
e[ne].to=u;
e[ne].v=w;
e[ne].next=head[v];
head[v]=ne;
}
inline void spfa()
{
memset(dis,0x3f,sizeof(dis));
int i,t=,w=;
dis[]=q[w]=;flag[]=;
while(t!=w)
{
int u=q[t++];
flag[u]=;
if(t==M)t=;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(dis[v]>dis[u]+e[i].v)
{
dis[v]=dis[u]+e[i].v;
if(flag[v]==)
{
flag[v]=;
q[w++]=v;
if(w==M)w=;
}
}
}
}
}
int main()
{
n=read();
m=read();
nm=(n*m-m-n+)<<;
int x;
for(int j=;j<m;j++)
{
x=read();
update(j,nm+,x);
}
for(int i=;i<n-;i++)
{
for(int j=;j<m;j++)
{
x=read();
update((i<<)*(m-)+j,((i<<)-)*(m-)+j,x);
}
}
for(int j=;j<m;j++)
{
x=read();
update(,((n<<)-)*(m-)+j,x);
}
for(int i=;i<n-;i++)
{
for(int j=;j<=m;j++)
{
x=read();
if(j==)
update(,(i<<)*(m-)+m,x);
else if(j==m)
update((i<<|)*(m-),nm+,x);
else
update((i<<)*(m-)+j-,(i<<)*(m-)+j+m-,x);
}
}
for(int i=;i<n-;i++)
{
for(int j=;j<m;j++)
{
x=read();
update((i<<|)*(m-)+j,(i<<)*(m-)+j,x);
}
}
spfa();
write(dis[nm+]);
return ;
}