【AtCoder】【DP】【思維】 Salvage Robots(AG004)

題意:

有一個n*m的矩陣,每一個格子中只會含有以下的字符:’.'表示位置爲空,'o’表示這個位置有一個機器人,'E’表示這個位置爲出口。保證出口只會出現一次。
現在你可以命令讓所有的機器人同時向上或下或左或右移動一步。如果這個機器人掉出了矩陣,那麼就無法營救這個機器人了,並且這個機器人會消失;如果這個機器人到達了Exit,那麼它就被成功營救了,也會立即消失。
現在要你求經過上述操作後能夠營救的機器人最多能有多少個。

數據範圍:

1<=n,m<=100

思路:

這道題首先需要轉化一下。原題中將機器人移上移下的操作看起來難以維護,那麼我們可以考慮機器人與出口之間的相對位置,那麼就可以轉變爲是出口在矩陣中跑來跑去,還帶着一個固定大小的框,一旦在框外的機器人就會掉出矩陣。
那麼這樣子就可以來定義狀態了。(這裏借用一下官方題解的圖)
在這裏插入圖片描述
上面圖片的黃色局域指的就是出口能夠走到的所有的區域。
定義dp[l][r][u][d]爲出口距離這個區域上邊界的區域,距離這個區域下邊界的區域,距離這個區域左邊界的區域,距離這個區域右邊界的區域。有可能會開不下,後面會詳細解釋。
那麼這樣子肯定有一些區域已經無法到達了:
在這裏插入圖片描述
紅色區域就是已經無法到達的區域。
對於白色的格子,是出口既沒有到達過,也不會有機器人掉出矩陣的地方;
對於只有紅色的格子,是機器人已經掉出矩陣,並且不會有出口到達的地方;
對於只有黃色的格子,是機器人已經被營救,並且也永遠都不會掉出去的地方;
對於又有紅色,又有黃色的格子,是出口能夠到達,並且最終如果有機器人,也會掉出去的地方。那麼對於這種格子,裏面的機器人是否被營救是不確定的。
那麼轉移必然是對於白色格子進行的。
在這裏插入圖片描述
對於當前這個圖而言,可以沿着紫色或者是綠色的區域進行轉移。那麼造成的效果在狀態上體現出來就是d+1或者是r+1,在值體現出來就是加上紫色區域的機器人,或者是加上綠色區域的機器人。
也就是:
dp[l][r][u][d+1]=dp[l][r][u][d]+sum[purple]dp[l][r][u][d+1]=dp[l][r][u][d]+sum[purple]
dp[l][r+1][u][d]=dp[l][r][u][d]+sum[green]dp[l][r+1][u][d]=dp[l][r][u][d]+sum[green]
當然對於一個狀態來說,這樣的轉移可能並不是完全的,因爲還有可能向着l+1或者是u+1的方向進行,只是這裏被舍掉了而已,因爲左邊和上面沒有白色的格子,是無法轉移的。
對於sum[purple]和sum[green]的處理可以簡單的對於每一行,每一列處理一個前綴和求出。
如果還有不懂的,可以看代碼裏面的解釋:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 100
using namespace std;
char Map[MAXN+5][MAXN+5];
int n,m,ex,ey;
int sumr[MAXN+5][MAXN+5],sumc[MAXN+5][MAXN+5];
int dp[MAXN+5][MAXN+5][MAXN+5];
void Init()
{
	memset(dp,-1,sizeof(dp));
}
int main()
{
//	freopen("robot.in","r",stdin);
//	freopen("robot.out","w",stdout);
	Init();
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",Map[i]+1);
		for(int j=1;j<=m;j++)
		{
		//對於每一行或每一列單獨計算前綴和
			sumr[i][j]=sumr[i][j-1];
			sumc[i][j]=sumc[i-1][j];
			if(Map[i][j]=='E')
				ex=i,ey=j;
			else if(Map[i][j]=='o')
			{
				sumr[i][j]++;
				sumc[i][j]++;
			}
		}
	}
	int L,R,U,D,ans=0;
	//L,R,U,D表示最大的l,r,u,d能夠達到的範圍,也就是出口距離整個矩陣的上下左右的距離
	L=ey-1;
	R=m-ey;
	U=ex-1;
	D=n-ex;
	dp[0][0][0]=0;
	for(int l=0;l<=L;l++)
		for(int r=0;r<=R;r++)
			for(int u=0;u<=U;u++)
				for(int d=0;d<=D;d++)
				{
					if(dp[r][u][d]==-1)
						continue;
					ans=max(dp[r][u][d],ans);
					//這裏的up,down,left,right是黃色區域的上下左右邊界
					//因爲向下走d步到達黃色區域的下邊界,上面也會因爲框的移動而產生出d的紅色區域
					//那麼就是說不含有紅色的區域的上界就是d+1。而還有一種上界的可能就是ex向上走u步
					//也就是正常的黃色區域(ex-u)。
					//那麼兩者取一個交集,也就是含有黃色,並且不含有紅色的區域就是max(ex-u,d+1)了
					//可以結合上面的圖來看,之後的down,left,right也是一樣的
					int up=max(ex-u,d+1);
					int down=min(ex+d,n-u);
					int left=max(ey-l,r+1);
					int right=min(ey+r,m-l);
					//紅色區域將出口這最後一塊聖土都吞併了,顯然狀態是非法的
					if(up>down||left>right)
						continue;
					int add;
					//u是上面的黃色區域的大小,U-d是上面的除掉紅色區域之後的大小
					//如果u<剩餘區域的大小,才能夠有白色區域,後面同理
					if(u<U-d)
					{
						add=sumr[ex-u-1][right]-sumr[ex-u-1][left-1];
						dp[r][u+1][d]=max(dp[r][u+1][d],dp[r][u][d]+add);
					}
					if(d<D-u)
					{
						add=sumr[ex+d+1][right]-sumr[ex+d+1][left-1];
						dp[r][u][d+1]=max(dp[r][u][d+1],dp[r][u][d]+add);
					}
					if(r<R-l)
					{
						add=sumc[down][ey+r+1]-sumc[up-1][ey+r+1];
						dp[r+1][u][d]=max(dp[r+1][u][d],dp[r][u][d]+add);
					}
					if(l<L-r)
					{
						add=sumc[down][ey-l-1]-sumc[up-1][ey-l-1];
						dp[r][u][d]=max(dp[r][u][d],dp[r][u][d]+add);
					}
				}
	printf("%d\n",ans);
	return 0;
}

下面是卡內存的問題。有兩種方法,一種是使用short int,還有一種是將[l]那一位省掉。
只需要保證在循環色最外層枚舉l,在最後一步進行dp[l+1][r][u][d]的轉移就可以了。這樣子就可以保證後面dp[r][u][d]這個狀態不會用來轉移,存的值也就是dp[l+1][r][u][d]的值。上面的代碼也是這樣子實現的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章