洛谷传送门
BZOJ传送门
题目描述
现在小朋友们最喜欢的"喜羊羊与灰太狼",话说灰太狼抓羊不到,但抓兔子还是比较在行的,而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对下面这样一个网格的地形:
左上角点为 ( 1 , 1 ) (1,1) (1,1),右下角点为 ( N , M ) (N,M) (N,M)(上图中 N = 3 , M = 4 N=3,M=4 N=3,M=4).有以下三种类型的道路
1: ( x , y ) < = > ( x + 1 , y ) (x,y)< =>(x+1,y) (x,y)<=>(x+1,y)
2: ( x , y ) < = > ( x , y + 1 ) (x,y)< = >(x,y+1) (x,y)<=>(x,y+1)
3: ( x , y ) < = > ( x + 1 , y + 1 ) (x,y)< = >(x+1,y+1) (x,y)<=>(x+1,y+1)
道路上的权值表示这条路上最多能够通过的兔子数,道路是无向的. 左上角和右下角为兔子的两个窝,开始时所有的兔子都聚集在左上角 ( 1 , 1 ) (1,1) (1,1)的窝里,现在它们要跑到右下角 ( N , M ) (N,M) (N,M)的窝中去,狼王开始伏击这些兔子.当然为了保险起见,如果一条道路上最多通过的兔子数为 K K K,狼王需要安排同样数量的 K K K只狼,才能完全封锁这条道路,你需要帮助狼王安排一个伏击方案,使得在将兔子一网打尽的前提下,参与的狼的数量要最小。因为狼还要去找喜羊羊麻烦。
输入输出格式
输入格式:
第一行为 N , M N,M N,M.表示网格的大小, N , M N,M N,M均小于等于 1000 1000 1000.
接下来分三部分
第一部分共 N N N行,每行 M − 1 M-1 M−1个数,表示横向道路的权值.
第二部分共 N − 1 N-1 N−1行,每行 M M M个数,表示纵向道路的权值.
第三部分共 N − 1 N-1 N−1行,每行 M − 1 M-1 M−1个数,表示斜向道路的权值.
输出格式:
输出一个整数,表示参与伏击的狼的最小数量.
输入输出样例
输入样例#1:
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
输出样例#1:
14
解题分析
大力网络流?乖乖 T L E TLE TLE…但貌似有很多人卡过了, 我就不评论了(各种玄学…)
观察这个图有个奇妙的性质: 最小割一定是一堆连续三角形的边。 如下图染色三角形:
然后我们可以惊奇的发现所有的合法的三角形集合都是从右边或上面开始,左边或下边结束(因为有向右下斜的路径,必须堵住。)
再仔细看可以发现实际上我们的割边都是三角形之间的共边, 所以我们完全可以将一个三角形抽象为一个点, 相邻三角形连共边边长度的边。 对于最上方和最右方的点, 我们向超级源点 S S S连边, 最下方和最左方的点向超级汇点 T T T连边。
然后就转化为了一个最短路模型?然后就没有然后了…(pb_ds贼6)
事实上这道题背后有个定理:平面图最小割等于其对应的对偶图的最短路。但博主太菜了, 没法深入探究, 在此也只好作罢。
代码如下:
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <limits.h>
#include <ext/pb_ds/priority_queue.hpp>
#define R register
#define W while
#define gc getchar()
#define MX 2000500
#define IN inline
#define S 0
#define T 2000001
IN int read()
{
R int x = 0; R char c = gc;
W (!isdigit(c)) c = gc;
W (isdigit(c))
x = (x << 1) + (x << 3) + c - 48, c = gc;
return x;
}
struct INFO {int pos, dis;};
IN bool operator < (const INFO &x, const INFO &y)
{return x.dis > y.dis;}
__gnu_pbds::priority_queue <INFO> q;
__gnu_pbds::priority_queue <INFO>::point_iterator handle[MX];
struct Edge {int to, len, nex;} edge[MX << 3];
int head[MX], up[1005][1005], down[1005][1005], dis[MX];
bool vis[MX];
int cnt, n, m, arr;
IN void add(R int from, R int to, R int len)
{
edge[++cnt] = {to, len, head[from]}, head[from] = cnt;
edge[++cnt] = {from, len, head[to]}, head[to] = cnt;
}
void dij()
{
std::memset(dis, 63, sizeof(dis));
dis[S] = 0; q.push({S, 0});
INFO cur; R int now;
W (!q.empty())
{
cur = q.top(); q.pop(); now = cur.pos; vis[now] = true;
for (R int i = head[now]; i; i = edge[i].nex)
{
if(dis[edge[i].to] > dis[now] + edge[i].len)
{
dis[edge[i].to] = dis[now] + edge[i].len;
if(!vis[edge[i].to])
{
vis[edge[i].to] = true;
dis[edge[i].to] = dis[now] + edge[i].len;
handle[edge[i].to] = q.push({edge[i].to, dis[edge[i].to]});
}
else q.modify(handle[edge[i].to], {edge[i].to, dis[edge[i].to]});
}
}
}
}
int main(void)
{
n = read(), m = read();
if(n == 1 || m == 1)
{
int ans = INT_MAX;
if(m > n) std::swap(m, n);
for (R int i = 1; i < n; ++i) ans = std::min(read(), ans);
return printf("%d", ans), 0;
}
for (R int i = 1; i < n; ++i)
for (R int j = 1; j < m; ++j)
up[i][j] = ++arr, down[i][j] = ++arr;//up为一个小正方形中右上的三角形编号, down为左下的三角形编号。
for (R int i = 1; i < m; ++i) add(S, up[1][i], read());
for (R int i = 2; i < n; ++i)
for (R int j = 1; j < m; ++j)
add(down[i - 1][j], up[i][j], read());
for (R int i = 1; i < m; ++i) add(down[n - 1][i], T, read());
for (R int i = 1; i < n; ++i)
{
add(T, down[i][1], read());
for (R int j = 2; j < m; ++j) add(up[i][j - 1], down[i][j], read());
add(S, up[i][m - 1], read());
}
for (R int i = 1; i < n; ++i)
for (R int j = 1; j < m; ++j)
add(down[i][j], up[i][j], read());
dij(); printf("%d", dis[T]);
}