小明的老師應為小明平時的表現,要去小明家家訪,小明所住的城市可看做一個二維網格,其中字元#表示障礙,‘.’表示空地。小明的老師住在左上角(1,1),小明住在右下角(n,m)。小明的老師要去小明家玩。小明的老師如果走到空地側不需要任何代價,但是如果要走到障礙點則他需要先摧毀該障礙。每次可以走到相鄰的四個方向之一,即從(x,y)可以走到(x+1,y),(x-1,y),(x,y+1),(x,y-1)。小明的老師可以花費C[i]的代價將第i列的所有障礙都消滅。請幫小明的老師計算她去小明家最少需要多少代價。
輸入格式
第一行兩個正整數n,m,表示網格圖的大小
接下來n行,每行m個字元,描述網格圖,字元僅由‘.’和‘#’組成,保證起點一定是空地
接下來一行m個數,表示C[i]
1<=n,m<=100,1<=C[i]<=1e5
輸出格式
輸出一個整數
輸入/輸出例子1
輸入:
4 4
.##.
.#.#
.###
..#.
5 3 9 4
輸出:
7
樣例解釋
消除第2列和第4列的障礙,總代價為7
建模:要如何抽象的變成圖論,如何建圖
可以藉助虛擬點(代表某些特定的點,比如代表指定一列的點)完成一些複雜度較高的操作。
方法1:
建完圖之後跑個最短路就好了
建圖:
對於每一個非障礙點,可以向四周連邊,沒障礙邊權就是0,有障礙邊權是a[到四周的那一列],因為你想要從上一個點到達四周的新點,且新點這裡有障礙,你就得先破除新點的障礙,所以破除花費就是新點處在的列。
但是我們發現,對於同一列,如果相鄰三個點都有障礙,這個時候從第一個點到第二個點,然後從第二個點到第三個點,就會花費兩倍破這一列牆的花費,但是實際上只用花費一次這一列的破牆操作,這樣可以把整列破掉。我們想要處理一下怎麼防止重複算。
我們可以對於每一列,每個點都向每個點連一條邊權是a[這一列]的邊,但這樣複雜度就超限了
我們想,能不能用一個點,代表這一列的所有點,我們稱為特殊點,然後這樣就只需要這個特殊的點和這一列的點有關係就行了。
對每一列建立一個虛擬點,虛擬點向這一列連邊,邊權0,就可以有效解決。
但是虛擬點不能只有出度,什麼點可以進入虛擬點?
前一列向虛擬點連邊,邊權a[這一列],這樣也同時確保了不會算多破牆操作的花費
但是注意一下,第一列前面沒有列了,那隻能是第一列每個點向虛擬點連邊了。
#include <bits/stdc++.h> using namespace std; const int N=505, M=101, M2=250000; struct node { int v, w; bool operator <(const node &A) const { return w>A.w; }; }; int n, m, w[N], dis[N*N], vis[N*N]; int dx[]={0, 0, 1, -1}, dy[]={1, -1, 0, 0}; char c[N][N]; vector<node> a[N*N*2]; priority_queue<node> q; void dij() { memset(dis, 63, sizeof dis); memset(vis, 0, sizeof vis); dis[1*M+1]=0; q.push({1*M+1, 0}); while (!q.empty()) { int u=q.top().v; q.pop(); if (vis[u]) continue; vis[u]=1; for (int i=0; i<a[u].size(); i++) { int v=a[u][i].v, w=a[u][i].w; if (dis[v]>dis[u]+w) { dis[v]=dis[u]+w; q.push({v, dis[v]}); } } } } int main() { // freopen("1.in", "r", stdin); scanf("%d%d", &n, &m); for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) cin>>c[i][j]; for (int i=1; i<=m; i++) scanf("%d", &w[i]); for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) for (int k=0; k<4; k++) { int nx=i+dx[k], ny=j+dy[k]; if (nx>=1 && nx<=n && ny>=1 && ny<=m) { if (c[nx][ny]=='.') a[i*M+j].push_back({nx*M+ny, 0}); else a[i*M+j].push_back({nx*M+ny, w[ny]}); } } for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) { if (j==1) a[i*M+j].push_back({j+M2, w[j]}); else a[i*M+j-1].push_back({j+M2, w[j]}); a[j+M2].push_back({i*M+j, 0}); } //for (int i=1; i<=n; i++) a[i*m+1].push_back({1+M2, w[1]}); dij(); printf("%d", dis[n*M+m]); return 0; } /* 4 3 .## #.. ##. ... 1 2 3 */
方法2:
貪心思想
破這一列的障礙後,肯定不會往回走,因為可以直接到這一列的任意位置
建一個大點,代表這列的所有點
入度:這一列全部點,上一列全部點,邊權a[這一列]
出度:下一列可走點,邊權0
兩個差不多了,看懂方法一這個就ok了