第一次見到分層思想和網絡流聯系起來的題,淺錄一下
題目連結: P4009 汽車加油行駛問題
該題方法很多,但是網絡流應該是最能接受和了解的吧 (
由于汽油的影響,建圖變得不那麼容易,正常建圖又無法将位置和油量結合起來,是以考慮分層建圖
我們定義狀态 ( i , j , k ) (i,j,k) (i,j,k)代表走到點 ( i , j ) (i,j) (i,j)時剩餘油量為 k k k.
-
建立源點 S S S和彙點 T T T
S ⇒ ( 1 , 1 , K ) S \Rightarrow (1,1,K) S⇒(1,1,K)連容量為 1 1 1,費用為 0 0 0的邊
因為不确定到達終點時剩餘油量的多少
( n , n , 0 ∽ K ) ⇒ T (n,n,0 \backsim K) \Rightarrow T (n,n,0∽K)⇒T連容量為1,費用為 0 0 0的邊
-
對于已經放置加油站的點
由于是強制消費(嗚嗚),是以無論來之前是多少油都得加滿
( i , j , 0 ∽ K − 1 ) ⇒ ( i , j , K ) (i,j,0 \backsim K-1) \Rightarrow (i,j,K) (i,j,0∽K−1)⇒(i,j,K)連容量為 1 1 1,費用為 A A A的邊
此時已經為滿油狀态
( i , j , K ) (i,j,K) (i,j,K)向下一層的 ( i − 1 , j ) (i-1,j) (i−1,j)以及 ( i , j − 1 ) (i,j-1) (i,j−1)連容量為 1 1 1,費用為 B B B的邊
( i , j , K ) (i,j,K) (i,j,K)向下一層的 ( i + 1 , j ) (i+1,j) (i+1,j)以及 ( i , j + 1 ) (i,j+1) (i,j+1)連容量為 1 1 1,費用為 0 0 0的邊
-
對于沒有放置加油站的點
首先要明确一個問題,什麼時候必須放置加油站,情況隻有一個:走到沒加油站的位置并且沒油了
( i , j , 0 ) ⇒ ( i , j , K ) (i,j,0) \Rightarrow (i,j,K) (i,j,0)⇒(i,j,K)連容量為 1 1 1,費用為 A + C A+C A+C的邊
( i , j , 1 ∽ K ) (i,j,1 \backsim K) (i,j,1∽K)向下一層的 ( i − 1 , j ) (i-1,j) (i−1,j)以及 ( i , j − 1 ) (i,j-1) (i,j−1)連容量為 1 1 1,費用為 B B B的邊
( i , j , 1 ∽ K ) (i,j,1 \backsim K) (i,j,1∽K)向下一層的 ( i + 1 , j ) (i+1,j) (i+1,j)以及 ( i , j + 1 ) (i,j+1) (i,j+1)連容量為 1 1 1,費用為 0 0 0的邊
最大流對應下的最小費用即為所求
#include<bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 2e5, INF = 0x3f3f3f3f;
struct Edge {
int from, to, flow, cost;
Edge(int from, int to, int flow, int cost) : from(from), to(to), flow(flow), cost(cost) {}
};
struct Dinic {
int S, T, m;
int d[N], incf[N], pre[N];
bool vis[N];
vector< Edge > edges;
vector< int > G[N];
Dinic(int S, int T) : S(S), T(T) {}
void AddEdge(int from, int to, int flow, int cost) {
edges.push_back(Edge(from, to, flow, cost));
edges.push_back(Edge(to, from, 0, -cost));
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
bool SPFA() {
memset(d, 0x3f, sizeof d);
memset(incf, 0, sizeof incf);
memset(vis, false, sizeof vis);
queue<int> Q;
Q.push(S);
vis[S] = true, incf[S] = INF, d[S] = 0;
while (!Q.empty()) {
int x = Q.front();
Q.pop();
vis[x] = false;
for (int i = 0; i < G[x].size(); ++ i) {
Edge &e = edges[G[x][i]];
if (d[x] + e.cost < d[e.to] && e.flow) {
d[e.to] = d[x] + e.cost;
pre[e.to] = G[x][i];
incf[e.to] = min(incf[x], e.flow);
if (!vis[e.to]) {
vis[e.to] = true;
Q.push(e.to);
}
}
}
}
return d[T] != INF;
}
pair< int, int > MCMF() {
int maxflow = 0, mincost = 0;
while (SPFA()) {
maxflow += incf[T];
mincost += incf[T] * d[T];
for (int i = T; i != S; i = edges[pre[i] ^ 1].to) {
edges[pre[i]].flow -= incf[T];
edges[pre[i] ^ 1].flow += incf[T];
}
}
return {maxflow, mincost};
}
};
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n, K, A, B, C;
cin >> n >> K >> A >> B >> C;
auto get = [&] (int i, int j, int k) { return k * n * n + (i - 1) * n + j;};
auto f = [&] (int x, int y) { return x >= 1 && x <= n && y >= 1 && y <= n;};
//0~k層代表i格油
Dinic d(0, (K + 1) * n * n + 1);
d.AddEdge(d.S, get(1, 1, K), 1, 0);
for (int k = 0; k <= K; ++ k)
d.AddEdge(get(n, n, k), d.T, 1, 0);
for (int i = 1; i <= n; ++ i) {
for (int j = 1, x; j <= n; ++ j) {
cin >> x;
if (x) {
// 非滿油狀态向滿油狀态連邊
for (int k = 0; k < K; ++ k)
d.AddEdge(get(i, j, k), get(i, j, K), 1, A);
// 滿油向下一層相鄰點連邊
if (f(i - 1, j))
d.AddEdge(get(i, j, K), get(i - 1, j, K - 1), 1, B);
if (f(i, j - 1))
d.AddEdge(get(i, j, K), get(i, j - 1, K - 1), 1, B);
if (f(i + 1, j))
d.AddEdge(get(i, j, K), get(i + 1, j, K - 1), 1, 0);
if (f(i, j + 1))
d.AddEdge(get(i, j, K), get(i, j + 1, K - 1), 1, 0);
} else {
// 向相鄰點連邊
for (int k = K; k >= 1; -- k) {
if (f(i - 1, j))
d.AddEdge(get(i, j, k), get(i - 1, j, k - 1), 1, B);
if (f(i, j - 1))
d.AddEdge(get(i, j, k), get(i, j - 1, k - 1), 1, B);
if (f(i + 1, j))
d.AddEdge(get(i, j, k), get(i + 1, j, k - 1), 1, 0);
if (f(i, j + 1))
d.AddEdge(get(i, j, k), get(i, j + 1, k - 1), 1, 0);
}
// *隻有遇到沒油且沒加油站的時候,才會放置加油站并且加油
d.AddEdge(get(i, j, 0), get(i, j, K), 1, A + C);
}
}
}
cout << d.MCMF().second << '\n';
return 0;
}