Luogu P7863 「EVOI-RD1」飞鸟和蝉

题目描述

https://www.luogu.com.cn/problem/P7863

简要题意:给定一张 $n\times m$ 的四连通网格图,每个点有一个权值 $a_{i,j}$,每个点只能走到周围四连通中权值严格小于自己的点,或者花费 $a_{i,j}-a_{x,y}$ 的代价从 $(i,j)$ 走到 $(x,y)$,现给定起点 $(x_0,y_0)$,求最少使用多少次第二种操作使得从起点开始经过每个点恰好一次且最终最后到起点,在最小化第二种操作的情况下,最小化代价

$n,m\le 50$

Solution

注意到原题意相当于在给定的 $DAG$ 上选择最少的路径条数,现在的问题是如何解决代价最小

我们注意到这个代价的形式,发现非常如果选了 $k$ 条路径 $(s_i,t_i)$,那么最终的花费为 $\sum_{i=1}^ks_i-t_i$

我们考虑在将一条路径的花费拆开,发现这个东西就相当于这条路径上每条边 $(u,v)$ 的代价为 $a_u-a_v$

这样我们可以直接使用经典的 $DAG$ 最小路径覆盖来解决,将最大流改成最小费用最大流即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
#include <queue>
#define maxn 5010
#define maxm 51
#define INF 1000000000000000000
#define ll long long
using namespace std;

int dx[] = { 0, 0, 1, -1 };
int dy[] = { 1, -1, 0, 0 };

struct Edge {
int to, next, w, fi;
} e[10000000]; int c1, head[maxn];
inline void add_edge(int u, int v, int w, int fi) {
e[c1].to = v; e[c1].w = w; e[c1].fi = fi;
e[c1].next = head[u]; head[u] = c1++;
}

inline void Add_edge(int u, int v, int w, int fi) {
add_edge(u, v, w, fi); add_edge(v, u, 0, -fi);
}

bool vis[maxn]; int s, t;
int la[maxn], pre[maxn]; ll dis[maxn];
bool spfa() {
fill(dis, dis + maxn, INF); dis[s] = 0;
fill(vis, vis + maxn, 0); vis[s] = 1;
deque<int> Q; Q.push_front(s); pre[t] = -1;
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, fi = e[i].fi;
if (w > 0 && dis[v] > dis[u] + fi) {
dis[v] = dis[u] + fi; pre[v] = u; la[v] = i;
if (vis[v]) continue; vis[v] = 1;
if (Q.empty() || dis[v] <= dis[Q.front()]) Q.push_front(v);
else Q.push_back(v);
}
}
}
return ~pre[t];
}

int mf; ll mc;
void mcmf() {
while (spfa()) {
int fl = 1e9;
for (int now = t; now; now = pre[now]) fl = min(fl, e[la[now]].w);
for (int now = t; now; now = pre[now]) e[la[now]].w -= fl, e[la[now] ^ 1].w += fl;
mc += fl * dis[t]; mf += fl;
}
}

int n, m, x0, y0;
int a[maxm][maxm];

inline int id(int x, int y) { return (x - 1) * m + y; }

int main() { fill(head, head + maxn, -1);
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);

cin >> n >> m >> x0 >> y0; s = 0; t = 2 * n * m + 1;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) cin >> a[i][j];
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) {
Add_edge(s, id(i, j), 1, 0); Add_edge(id(i, j) + n * m, t, 1, 0);
for (int k = 0; k < 4; ++k) {
int x = i + dx[k], y = j + dy[k];
if (x < 1 || x > n || y < 1 || y > m) continue;
if (a[i][j] <= a[x][y]) continue;
Add_edge(id(i, j), id(x, y) + n * m, 1, a[i][j] - a[x][y]);
}
}
mcmf(); cout << n * m - mf << " " << mc << "\n";
return 0;
}