BZOJ 1001 [BeiJing2006] 狼抓兔子(平面图最大流)
题目大意
现在小朋友们最喜欢的"喜羊羊与灰太狼",话说灰太狼抓羊不到,但抓兔子还是比较在行的。而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对下面这样一个网格的地形:
左上角点为 (1, 1),右下角点为 (N, M) (上图中N=4, M=4)。有以下三种类型的道路
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 只狼,才能完全封锁这条道路,你需要帮助狼王安排一个伏击方案,使得在将兔子一网打尽的前提下,参与的狼的数量要最小。因为狼还要去找喜羊羊麻烦。
N, M 均小于等于 1000
做法分析
一看就是最小割问题,写的比较好的网络流可以直接暴力的搞过去,当然,正解肯定不是这样,虽然是一个水题,不过还是值得一做的
这题需要利用平面图的对偶图性质
平面图的对偶图很好构造:
将原图中的面变成新图中的点
原图中,每条边必定分割了两个面,在新图中,对应的点之间添加一条边,边权还是原图中边的边权
在原图中的一个全局的割就对应了新图中的一个环,也就是说,如果想要求原图中的一个全局最小割,只需要在新图中找一个最小环即可
怎么求出原图中分割固定点 s 和 t(s 和 t 处于一个无线大的平面的边缘) 的一个最小割呢?
先在原图中添加 s 到 t 的边,给原图增加了一个面
构造原图的对偶图,把由于增边而增加的新面对应的点设为 S,无穷大的平面对应的点设为 T
删掉对偶图中 S 到 T 直接相连的边
求出 S 到 T 的最短路就可以了
详细请看周冬的论文 《两极相通——浅析最大—最小定理在信息学竞赛中的应用》
这题按照上面将的建图,跑最短路就行了,听说 SPFA 也能过,我没试过,用的堆优化的 Dijkstra + 输入外挂直接 516ms 过掉
参考代码
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <queue> 5 6 using namespace std; 7 8 const int N=2000006, INF=0x3fffffff, E=N*3; 9 10 struct ARC { 11 int u, val, next; 12 inline void init(int a, int b, int c) { 13 u=a, val=b, next=c; 14 } 15 } arc[E]; 16 int head[N], tot, S, T, n, m, dis[N]; 17 bool vs[N]; 18 19 struct data { 20 int u, dis; 21 data() {} 22 data(int a, int b) : u(a), dis(b) {} 23 bool operator < (const data &T) const { 24 return dis>T.dis; 25 } 26 }; 27 28 inline void add_arc(int s, int t, int val) { 29 arc[tot].init(t, val, head[s]); 30 head[s]=tot++; 31 } 32 33 priority_queue <data> Q; 34 void Dijkstra() { 35 fill(dis, dis+T+1, INF); 36 fill(vs, vs+T+1, 0); 37 while(!Q.empty()) Q.pop(); 38 dis[S]=0, Q.push(data(S, 0)); 39 for(int u; !Q.empty(); ) { 40 u=Q.top().u, Q.pop(); 41 if(vs[u]) continue; 42 if(u==T) { 43 printf("%d\n", dis[T]); 44 break; 45 } 46 vs[u]=1; 47 for(int e=head[u]; e!=-1; e=arc[e].next) { 48 int v=arc[e].u; 49 if(vs[v] || dis[u]+arc[e].val>=dis[v]) continue; 50 dis[v]=dis[u]+arc[e].val; 51 Q.push(data(v, dis[v])); 52 } 53 } 54 } 55 56 void read(int &x) { 57 char c; 58 while((c=getchar())<'0' || c>'9'); 59 x=c-'0'; 60 while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0'; 61 } 62 63 void Input() { 64 for(int i=0, id1, id2, a; i<=n-1; i++) 65 for(int j=1; j<=m-1; j++) { 66 read(a); 67 id1=((i-1)*(m-1)+j)*2-1; 68 id2=(i*(m-1)+j)*2; 69 if(i==0) id1=T; 70 else if(i==n-1) id2=S; 71 add_arc(id1, id2, a); 72 add_arc(id2, id1, a); 73 } 74 75 for(int i=1, id1, id2, a; i<=n-1; i++) 76 for(int j=0; j<m; j++) { 77 read(a); 78 id1=((i-1)*(m-1)+j)*2; 79 id2=((i-1)*(m-1)+j+1)*2-1; 80 if(j==0) id1=S; 81 else if(j==m-1) id2=T; 82 add_arc(id1, id2, a); 83 add_arc(id2, id1, a); 84 } 85 86 for(int i=1, id1, id2, a; i<=n-1; i++) 87 for(int j=1; j<=m-1; j++) { 88 read(a); 89 id1=((i-1)*(m-1)+j)*2; 90 id2=((i-1)*(m-1)+j)*2-1; 91 add_arc(id1, id2, a); 92 add_arc(id2, id1, a); 93 } 94 } 95 96 int main() { 97 read(n), read(m); 98 S=0, T=(n-1)*(m-1)*2+1; 99 fill(head, head+T+1, -1), tot=0; 100 if(n==1 || m==1) { 101 if(n>m) swap(n, m); 102 int ans=INF; 103 for(int i=1, a; i<m; i++) { 104 read(a); 105 if(ans>a) ans=a; 106 } 107 printf("%d\n", ans==INF?0:ans); 108 } 109 else Input(), Dijkstra(); 110 return 0; 111 }
题目链接 & AC 通道