狼抓兔子——平面图转对偶图
题目描述
现在小朋友们最喜欢的"喜羊羊与灰太狼",话说灰太狼抓羊不到,但抓兔子还是比较在行的,而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对下面这样一个网格的地形:
左上角点为(1,1),右下角点为(N,M)(上图中N=3,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.表示网格的大小,N,M均小于等于1000.
接下来分三部分
第一部分共N行,每行M-1个数,表示横向道路的权值.
第二部分共N-1行,每行M个数,表示纵向道路的权值.
第三部分共N-1行,每行M-1个数,表示斜向道路的权值.
输出格式:
输出一个整数,表示参与伏击的狼的最小数量.
输入输出样例
输出样例#1:
14
做这个题,第一反应肯定是网络流求最小割,但是一看数据范围就会发现,网络流会爆炸的,所以我们就通过对偶图把问题转换为求最短路
下面是代码(%%%dzy)
// luogu-judger-enable-o2 #include<iostream> #include<cstdio> #include<cstring> #include<queue> #define maxn 2000010 #define INF 1000000000 using namespace std; int n, m, a[1010][1010], b[1010][1010], c[1010][1010], id[1010][1010][2], s, t, cnt; inline int read(){ int x = 0; char c = getchar(); while(c < '0' || c > '9') c = getchar(); while(c >= '0' && c <= '9'){x = x * 10 + c - '0'; c = getchar();} return x; } //10^5以上要用快读,不然会炸 struct Edge{ int to, next, w; }e[3 * maxn]; int sz, head[maxn]; inline void add_edge(int u, int v, int w){ e[sz].to = v; e[sz].w = w; e[sz].next = head[u]; head[u] = sz++; e[sz].to = u; e[sz].w = w; e[sz].next = head[v]; head[v] = sz++; }//因为是无向图,所以同时存两条边 int dis[maxn]; deque<int> Q; bool vis[maxn]; void spfa(){ memset(dis, 10, sizeof dis); vis[s] = 1; Q.push_back(s); dis[s] = 0; while(!Q.empty()){ int u = Q.front(); Q.pop_front(); vis[u] = 0; for(int i = head[u]; ~i; i = e[i].next){ int v = e[i].to, w = e[i].w; if(dis[v] > dis[u] + w){ dis[v] = dis[u] + w; if(vis[v]) continue; vis[v] = 1; if(Q.empty() || dis[v] <= dis[Q.front()]) Q.push_front(v); else Q.push_back(v); } } } cout << dis[t]; } int main(){ memset(head, -1, sizeof head); n = read(); m = read(); s = 2 * (n - 1) * (m - 1) + 1, t = s + 1; if(n == 1 || m == 1){ if(n < m) swap(n, m); int ans = INF; for(int i = 1; i < n; ++i) ans = min(ans, read()); cout << ans; return 0; //如果只有一行的话,我特判一下输出最小值即可 } for(int i = 1; i <= n; ++i) for(int j = 1; j < m; ++j) a[i][j] = read(); for(int i = 1; i < n; ++i) for(int j = 1; j <= m; ++j) b[i][j] = read(); for(int i = 1; i < n; ++i) for(int j = 1; j < m; ++j){ c[i][j] = read(); id[i][j][0] = ++cnt; id[i][j][1] = ++cnt; //用id[i][j][0]表示正方形里上面的点 ,id[i][j][1]表示下面的点,每一个方格属于哪两个点要写明白 } //把每一个值都存起来,在连边的时候就可以离线做了 for(int i = 1; i < n; ++i) for(int j = 1; j < m; ++j) add_edge(id[i][j][1], id[i][j][0], c[i][j]); //斜着的边可以直接连 for(int i = 1; i <= n; ++i) for(int j = 1; j < m; ++j){ if(i == 1) add_edge(id[i][j][0], t, a[i][j]); else if(i == n) add_edge(s, id[i - 1][j][1], a[i][j]); else add_edge(id[i][j][0], id[i - 1][j][1], a[i][j]); } //横向的边要进行特判,第一行的上点与起点相连,最后一行的下点与终点相连 for(int i = 1; i < n; ++i) for(int j = 1; j <= m; ++j){ if(j == 1) add_edge(s, id[i][j][1], b[i][j]); else if(j == m) add_edge(id[i][j - 1][0], t, b[i][j]); else add_edge(id[i][j][1], id[i][j - 1][0], b[i][j]); } //竖向的边也是要特判,同理可得 spfa(); //spfa跑最短路即可 //最好还是用dijkstra写 ,因为稠密图用spfa跑还是非常容易炸的,但是现在bzoj和luogu都能A,所以我就不改dijkstra了 return 0; }
我博客里有大量的从别的博客复制过来的代码,分析,以及理解,但我一律会在文章后面标记原博客大佬博客名,其中部分会加以连接。
绝无抄袭的意思,只是为了我在复习的时候找博客方便。
如有原作者对此有不满,请在博客留言,我一定会删除该博文。