題意:
有一個n*m的矩陣,每一個格子中只會含有以下的字符:’.'表示位置爲空,'o’表示這個位置有一個機器人,'E’表示這個位置爲出口。保證出口只會出現一次。
現在你可以命令讓所有的機器人同時向上或下或左或右移動一步。如果這個機器人掉出了矩陣,那麼就無法營救這個機器人了,並且這個機器人會消失;如果這個機器人到達了Exit,那麼它就被成功營救了,也會立即消失。
現在要你求經過上述操作後能夠營救的機器人最多能有多少個。
數據範圍:
1<=n,m<=100
思路:
這道題首先需要轉化一下。原題中將機器人移上移下的操作看起來難以維護,那麼我們可以考慮機器人與出口之間的相對位置,那麼就可以轉變爲是出口在矩陣中跑來跑去,還帶着一個固定大小的框,一旦在框外的機器人就會掉出矩陣。
那麼這樣子就可以來定義狀態了。(這裏借用一下官方題解的圖)
上面圖片的黃色局域指的就是出口能夠走到的所有的區域。
定義dp[l][r][u][d]爲出口距離這個區域上邊界的區域,距離這個區域下邊界的區域,距離這個區域左邊界的區域,距離這個區域右邊界的區域。有可能會開不下,後面會詳細解釋。
那麼這樣子肯定有一些區域已經無法到達了:
紅色區域就是已經無法到達的區域。
對於白色的格子,是出口既沒有到達過,也不會有機器人掉出矩陣的地方;
對於只有紅色的格子,是機器人已經掉出矩陣,並且不會有出口到達的地方;
對於只有黃色的格子,是機器人已經被營救,並且也永遠都不會掉出去的地方;
對於又有紅色,又有黃色的格子,是出口能夠到達,並且最終如果有機器人,也會掉出去的地方。那麼對於這種格子,裏面的機器人是否被營救是不確定的。
那麼轉移必然是對於白色格子進行的。
對於當前這個圖而言,可以沿着紫色或者是綠色的區域進行轉移。那麼造成的效果在狀態上體現出來就是d+1或者是r+1,在值體現出來就是加上紫色區域的機器人,或者是加上綠色區域的機器人。
也就是:
當然對於一個狀態來說,這樣的轉移可能並不是完全的,因爲還有可能向着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]的值。上面的代碼也是這樣子實現的。