家訪(圖論建模)

cn是大帅哥886發表於2024-08-24

小明的老師應為小明平時的表現,要去小明家家訪,小明所住的城市可看做一個二維網格,其中字元#表示障礙,‘.’表示空地。小明的老師住在左上角(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了

相關文章