BZOJ1001: [BeiJing2006]狼抓兔子(优化的dinic或转化对偶图求最短路)
1001: [BeiJing2006]狼抓兔子
Time Limit: 15 Sec Memory Limit: 162 MBSubmit: 30078 Solved: 7908
[Submit][Status][Discuss]
Description
现在小朋友们最喜欢的"喜羊羊与灰太狼",话说灰太狼抓羊不到,但抓兔子还是比较在行的,
而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对下面这样一个网格的地形:
左上角点为(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
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
题解1(网络流最大流dinic):
题意是求一个无向图的最小割,然后我们运用最小割-最大流定理,可以转化成求最大流的问题。不过朴素的Dinic是会TLE的,这里提供一种优化方法:
我们知道,假定在一次dinic过程中,发现不能再进行增广了,那么就相当于向下的这条路是废的。因此,我们可以直接把这条路堵上,然后就可以过了。
参考代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 inline void read(int &x) 4 { 5 x = 0; char c = getchar(); 6 while(!isdigit(c)) c = getchar(); 7 while(isdigit(c)) x = (x << 3) + (x << 1) + c - '0', c = getchar(); 8 } 9 #define MAXN 1003 10 struct node{ 11 int fr, to, va, nxt; 12 }edge[MAXN * MAXN * 6]; 13 int head[MAXN * MAXN], cnt; 14 inline void add_edge(int u, int v, int w) { 15 edge[cnt].fr = u, edge[cnt].to = v, edge[cnt].va = w; 16 edge[cnt].nxt = head[u], head[u] = cnt++; 17 edge[cnt].fr = v, edge[cnt].to = u, edge[cnt].va = w; 18 edge[cnt].nxt = head[v], head[v] = cnt++; //反向边初始化 19 } 20 int st, ed, rank[MAXN * MAXN]; 21 int BFS() { 22 queue<int> q; 23 memset(rank, 0, sizeof rank); 24 rank[st] = 1; 25 q.push(st); 26 while(!q.empty()) { 27 int tmp = q.front(); 28 //cout<<tmp<<endl; 29 q.pop(); 30 for(int i = head[tmp]; i != -1; i = edge[i].nxt) { 31 int o = edge[i].to; 32 if(rank[o] || edge[i].va <= 0) continue; 33 rank[o] = rank[tmp] + 1; 34 q.push(o); 35 } 36 } 37 return rank[ed]; 38 } 39 int dfs(int u, int flow) { 40 if(u == ed) return flow; 41 int add = 0; 42 for(int i = head[u]; i != -1 && add < flow; i = edge[i].nxt) { 43 int v = edge[i].to; 44 if(rank[v] != rank[u] + 1 || !edge[i].va) continue; 45 int tmpadd = dfs(v, min(edge[i].va, flow - add)); 46 if(!tmpadd) { //重要!就是这里! 47 rank[v] = -1; 48 continue; 49 } 50 edge[i].va -= tmpadd, edge[i ^ 1].va += tmpadd; 51 add += tmpadd; 52 } 53 return add; 54 } 55 int ans; 56 void dinic() { 57 while(BFS()) ans += dfs(st, 0x3fffff); 58 } 59 int n, m; 60 inline int gethash(int i, int j) { 61 return (i - 1) * m + j; 62 } 63 int main() { 64 memset(head, -1, sizeof head); 65 read(n), read(m); 66 int tmp; 67 st = 1, ed = gethash(n, m); 68 for(int i = 1; i <= n; ++i) { 69 for(int j = 1; j < m; ++j) 70 read(tmp), add_edge(gethash(i, j), gethash(i, j + 1), tmp); 71 } 72 for(int i = 1; i < n; ++i) { 73 for(int j = 1; j <= m; ++j) 74 read(tmp), add_edge(gethash(i, j), gethash(i + 1, j), tmp); 75 } 76 for(int i = 1; i < n; ++i) { 77 for(int j = 1; j < m; ++j) 78 read(tmp), add_edge(gethash(i, j), gethash(i + 1, j + 1), tmp); 79 } 80 dinic(); 81 cout<<ans<<endl; 82 return 0; 83 }
题解2(转化为对偶图求最短路):
先来观察下面的这张图:
我们发现,这么一张图,可以转化成任意两边的交点都在顶点上的形式。
像这样任意两边的交点在顶点上的图我们称为平面图。
几条边围成一个区域,这个区域称为一个面。
对平面图,我们定义对偶图:
下图中黑色的是个平面图,红色的就是对偶图。其建立方法是,对每个面建一个点,只要有一条边是在两个面之间,我们就对这两个面对应的点连边(稍有些绕)。注意是有一条边就连线。
然后我们就得到萌萌哒的对偶图一张!
对偶图就有很多美妙的性质了。比如说,我们发现,对偶图的一条边就对应了一条割边。
既然如此的话,想想狼抓兔子,一条割边有一个容量,那么如果我们建它的对偶图,最短路就是最小割。
所以得出下面的重要定理:
对平面图来说,最大流 = 最小割 = 对偶图最短路
所以我们就可以稳一些跑出来狼抓兔子。
参考代码:
1 //对偶图 2 #include<bits/stdc++.h> 3 using namespace std; 4 const int N=2000006; 5 const int INF=0x3fffffff; 6 int E=N*3; 7 struct ARC { 8 int u, val, next; 9 inline void init(int a, int b, int c) { 10 u=a, val=b, next=c; 11 } 12 } arc[E]; 13 int head[N], tot, S, T, n, m, dis[N]; 14 bool vs[N]; 15 16 struct data{ 17 int u, dis; 18 data() {} 19 data(int a, int b) : u(a), dis(b) {} 20 bool operator < (const data &T) const { 21 return dis>T.dis; 22 } 23 }; 24 25 inline void add_arc(int s, int t, int val) 26 { 27 arc[tot].init(t, val, head[s]); 28 head[s]=tot++; 29 } 30 31 priority_queue<data> Q; 32 void Dijkstra() 33 { 34 fill(dis, dis+T+1, INF); 35 fill(vs, vs+T+1, 0); 36 while(!Q.empty()) Q.pop(); 37 dis[S]=0, Q.push(data(S, 0)); 38 for(int u; !Q.empty(); ) 39 { 40 u=Q.top().u, Q.pop(); 41 if(vs[u]) continue; 42 if(u==T) 43 { 44 printf("%d\n", dis[T]); 45 break; 46 } 47 vs[u]=1; 48 for(int e=head[u]; e!=-1; e=arc[e].next) { 49 int v=arc[e].u; 50 if(vs[v] || dis[u]+arc[e].val>=dis[v]) continue; 51 dis[v]=dis[u]+arc[e].val; 52 Q.push(data(v, dis[v])); 53 } 54 } 55 } 56 57 void read(int &x) { 58 char c; 59 while((c=getchar())<'0' || c>'9'); 60 x=c-'0'; 61 while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0'; 62 } 63 64 void Input() { 65 for(int i=0, id1, id2, a; i<=n-1; i++) 66 for(int j=1; j<=m-1; j++) { 67 read(a); 68 id1=((i-1)*(m-1)+j)*2-1; 69 id2=(i*(m-1)+j)*2; 70 if(i==0) id1=T; 71 else if(i==n-1) id2=S; 72 add_arc(id1,id2,a); 73 add_arc(id2,id1,a); 74 } 75 76 for(int i=1, id1, id2, a; i<=n-1; i++) 77 for(int j=0; j<m; j++) { 78 read(a); 79 id1=((i-1)*(m-1)+j)*2; 80 id2=((i-1)*(m-1)+j+1)*2-1; 81 if(j==0) id1=S; 82 else if(j==m-1) id2=T; 83 add_arc(id1, id2, a); 84 add_arc(id2, id1, a); 85 } 86 87 for(int i=1, id1, id2, a; i<=n-1; i++) 88 for(int j=1; j<=m-1; j++) { 89 read(a); 90 id1=((i-1)*(m-1)+j)*2; 91 id2=((i-1)*(m-1)+j)*2-1; 92 add_arc(id1, id2, a); 93 add_arc(id2, id1, a); 94 } 95 } 96 97 int main() { 98 read(n), read(m); 99 S=0, T=(n-1)*(m-1)*2+1; 100 fill(head, head+T+1, -1), tot=0; 101 if(n==1 || m==1) 102 { 103 if(n>m) swap(n, m); 104 int ans=INF; 105 for(int i=1, a; i<m; i++) 106 { 107 read(a); 108 if(ans>a) ans=a; 109 } 110 printf("%d\n", ans==INF?0:ans); 111 } 112 else Input(), Dijkstra(); 113 return 0; 114 }