[BZOJ1001][BeiJing2006]狼抓兔子(最小割转最短路|平面图转对偶图)
1001: [BeiJing2006]狼抓兔子
Time Limit: 15 Sec Memory Limit: 162 MBSubmit: 31805 Solved: 8494
[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新加数据一组,可能会卡掉从前可以过的程序。
Solution
一眼一个最小割。以为可以复习一下板子了,可以看这边数N*N*3不对呀,直接跑最小割会炸的。
于是就翻题解学到了平面图转对偶图的神奇操作。
平面图可以参考这篇博文 https://www.cnblogs.com/lfri/p/9939463.html
平面图转对偶图的方法可以参考这篇博文 https://www.cnblogs.com/qzqzgfy/p/5578785.html
像题目中这种源点和汇点在无界面的边界上的平面图叫做s-t平面图。
在这种图上可以实现求最小割转求最短路。
需要注意的是实际操作时要先把s到t连条虚边,把原图的边界的面分成两个部分,也就是说多了一个附加面作为s。
实现时主要是要给每个面(也就是对偶图中的点)编好号,代码中有注释。
Code
#include<bits/stdc++.h> using namespace std; typedef pair<int,int> pi; //注意空间要开够 const int N=1005*1005*2; inline int read(){ int x=0,w=0;char ch=0; while(!isdigit(ch)) w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return w?-x:x; } struct edge{ int v,w,last; }e[N*3]; int tot,tail[N]; inline void add(int x,int y,int z){ e[++tot]=(edge){y,z,tail[x]}; tail[x]=tot; e[++tot]=(edge){x,z,tail[y]}; tail[y]=tot; } int n,m,s,t,base; //一个小正方形的下三角为(i-1)*(m-1)+j,上三角加个base bool check(int i,int j){return i>=1&&i<=n-1&&j>=1&&j<=m-1;} int down(int i,int j){return check(i,j)?(i-1)*(m-1)+j:s;} int up(int i,int j){return check(i,j)?down(i,j)+base:t;} void build(){ s=0,base=(n-1)*(m-1),t=base<<1|1; //原平面图的面有(n-1)*(m-1)*2+1个,再加个编号为0的附加面 for(int i=1;i<=n;++i) for(int j=1;j<m;++j) add(down(i-1,j),up(i,j),read()); for(int i=1;i<n;++i) for(int j=1;j<=m;++j) add(up(i,j-1),down(i,j),read()); for(int i=1;i<n;++i) for(int j=1;j<m;++j) add(up(i,j),down(i,j),read()); } //这只是一个普通的堆优化dijkstra bool vis[N]; int d[N]; void dij(){ priority_queue<pi,vector<pi>,greater<pi> > q; memset(d,0x3f,sizeof d);d[s]=0; q.push(make_pair(d[s],s)); while(!q.empty()){ int x=q.top().second;q.pop(); if(vis[x]) continue;vis[x]=true; for(int p=tail[x];p;p=e[p].last){ int &y=e[p].v,&w=e[p].w; if(d[y]>d[x]+w){ d[y]=d[x]+w; q.push(make_pair(d[y],y)); } } } } int main(){ n=read(),m=read(); build();dij(); cout<<d[t]<<endl; return 0; }
愿你有一天能和重要的人重逢