[NOI2010]海拔 题解
题目地址:洛谷:【P2046】[NOI2010]海拔 – 洛谷、BZOJ:Problem 2007. — [Noi2010]海拔
题目描述
YT市是一个规划良好的城市,城市被东西向和南北向的主干道划分为n×n个区域。简单起见,可以将YT市看作 一个正方形,每一个区域也可看作一个正方形。从而,YT城市中包括(n+1)×(n+1)个交叉路口和2n×(n+1)条双向道路(简称道路),每条双向 道路连接主干道上两个相邻的交叉路口。下图为一张YT市的地图(n = 2),城市被划分为2×2个区域,包括3×3个交叉路口和12条双向道路。
小Z作为该市的市长,他根据统计信息得到了每天上班高峰期间YT市每条道路两个方向的人流量,即在高峰期间沿 着该方向通过这条道路的人数。每一个交叉路口都有不同的海拔高度值,YT市市民认为爬坡是一件非常累的事情,每向上爬h的高度,就需要消耗h的体力。如果 是下坡的话,则不需要耗费体力。因此如果一段道路的终点海拔减去起点海拔的值为h(注意h可能是负数),那么一个人经过这段路所消耗的体力是max{0, h}(这里max{a, b}表示取a, b两个值中的较大值)。
小Z还测量得到这个城市西北角的交叉路口海拔为0,东南角的交叉路口海拔为1(如上图所示),但其它交叉路口的海拔高度都无法得知。小Z想知道在最理想的情况下(即你可以任意假设其他路口的海拔高度),每天上班高峰期间所有人爬坡消耗的总体力和的最小值。
输入输出格式
输入格式:
第一行包含一个整数n,含义如上文所示。
接下来4n(n + 1)行,每行包含一个非负整数分别表示每一条道路每一个方向的人流量信息。输入顺序:n(n + 1)个数表示所有从西到东方向的人流量,然后n(n + 1)个数表示所有从北到南方向的人流量,n(n + 1)个数表示所有从东到西方向的人流量,最后是n(n + 1)个数表示所有从南到北方向的人流量。对于每一个方向,输入顺序按照起点由北向南,若南北方向相同时由西到东的顺序给出(参见样例输入)。
输出格式:
仅包含一个数,表示在最理想情况下每天上班高峰期间所有人爬坡所消耗的总体力和(即总体力和的最小值),结果四舍五入到整数。
输入输出样例
输入样例#1:
1 1 2 3 4 5 6 7 8
输出样例#1:
3
说明
对于20%的数据:n ≤ 3;
对于50%的数据:n ≤ 15;
对于80%的数据:n ≤ 40;
对于100%的数据:1 ≤ n ≤ 500,0 ≤ 流量 ≤ 1,000,000且所有流量均为整数。
题解
我们发现这些方格在最优解上的高度肯定非0即1,且有一条明显的分界线。因此我们只需要找出一些跨越分界线的边,计算它们的贡献即可。这实际上可以看成一个最小割。
既然我们把原问题转化成了最小割问题,就可以继续转化为对偶图上的最短路问题。由于是方格图,对偶图很好建立。
需要注意的是双向边问题,我们只需要在对偶图上也建立方向相反权值不同的有向边即可。例如我们将向下向右的边建为S→T方向,则向上向左的边要建为T→S方向,随后跑一遍最短路即可。通过验证,S、T的选择与边的方向选择的两种方案是等价的,因此可以任意选择S和T的位置。下面的代码中,上边界和右边界与T相连,下边界和左边界与S相连。
代码
// Code by KSkun, 2018/4
#include <cstdio>
#include <cstring>
#include <queue>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF
: *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
register char c = fgc();
while(c < '0' || c > '9') {
if(c == '-') neg = -1;
c = fgc();
}
while(c >= '0' && c <= '9') {
res = res * 10 + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 1000005;
struct Edge {
int to, w, nxt;
} gra[MAXN << 1];
int head[MAXN], tot;
inline void addedge(int u, int v, int w) {
gra[tot] = Edge {v, w, head[u]}; head[u] = tot++;
}
int dis[MAXN];
bool inque[MAXN];
struct Queue {
int l, r, q[MAXN];
inline void clear() {
l = r = 0;
}
Queue() {
clear();
}
inline int front() {
return q[l];
}
inline void push(int x) {
q[r++] = x;
if(r >= MAXN) r -= MAXN;
}
inline void pop() {
l++;
if(l >= MAXN) l -= MAXN;
}
inline bool empty() {
return l == r;
}
} que;
inline void spfa(int s) {
que.clear();
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0; inque[s] = true;
que.push(s);
while(!que.empty()) {
register int u = que.front(); que.pop(); inque[u] = false;
for(int i = head[u]; ~i; i = gra[i].nxt) {
register int v = gra[i].to;
if(dis[v] > dis[u] + gra[i].w) {
dis[v] = dis[u] + gra[i].w;
if(!inque[v]) {
inque[v] = true;
que.push(v);
}
}
}
}
}
int n, t, S, T;
int main() {
memset(head, -1, sizeof(head));
n = readint();
S = 0; T = n * n + 1;
for(register int i = 1; i <= n + 1; i++) {
for(register int j = 1; j <= n; j++) {
t = readint();
if(i == 1) {
addedge((i - 1) * n + j, T, t);
} else if(i == n + 1) {
addedge(S, (i - 2) * n + j, t);
} else {
addedge((i - 1) * n + j, (i - 2) * n + j, t);
}
}
}
for(register int i = 1; i <= n; i++) {
for(register int j = 1; j <= n + 1; j++) {
t = readint();
if(j == 1) {
addedge(S, (i - 1) * n + j, t);
} else if(j == n + 1) {
addedge((i - 1) * n + j - 1, T, t);
} else {
addedge((i - 1) * n + j - 1, (i - 1) * n + j, t);
}
}
}
for(register int i = 1; i <= n + 1; i++) {
for(register int j = 1; j <= n; j++) {
t = readint();
if(i == 1) {
addedge(T, (i - 1) * n + j, t);
} else if(i == n + 1) {
addedge((i - 2) * n + j, S, t);
} else {
addedge((i - 2) * n + j, (i - 1) * n + j, t);
}
}
}
for(register int i = 1; i <= n; i++) {
for(register int j = 1; j <= n + 1; j++) {
t = readint();
if(j == 1) {
addedge((i - 1) * n + j, S, t);
} else if(j == n + 1) {
addedge(T, (i - 1) * n + j - 1, t);
} else {
addedge((i - 1) * n + j, (i - 1) * n + j - 1, t);
}
}
}
spfa(S);
printf("%d", dis[T]);
return 0;
}