1. 程式人生 > >[BZOJ 1556]墓地秘密

[BZOJ 1556]墓地秘密

n) rom ram 最好的 大型 一聲 string 最小 功夫

Description
費盡周折,終於將眾將士的殘骸運送到了KD軍事基地地底層的大型墓地入口。KD的夥伴和戰友們都參加了這次重大的送葬儀式。右邊是一扇敞開的大門,進去便是墓地了,左邊是一堵凹進去的墻,沒有什麽特別的地方。 部隊緩緩進入右邊的門,一切。。。就這麽結束了麽。。。。。 此時, F卻沒有跟上隊伍,在一般MM都會有的強烈的第六感之下,她來到了左邊這堵墻前一探究竟。掃去了重重的灰塵之後,墻上一塊凹進去的手掌印清晰可見了。F試著用自己的手對上去,竟剛好合適。稍微用力一按,頓時一聲巨響,地上馬上裂開一大洞,F和那厚重的墻瞬間一起落入深淵!當其他人聽見了巨大的聲響而趕來的時候,一切都恢復平靜了。只有那堵墻後面的世界,震驚了所有生物。這到底是什麽,為什麽會在墓地裏面? 墻的後面是一個巨大的迷宮!簡單的一行字浮現在了一側的墻上:猛烈撞擊所有發亮的機關石。當大夥好奇的蜂擁進迷宮的時候,一塊莫名其妙的巨石竟從入口上方落下,將入口完全堵死了!石頭上清晰的寫了一行字:超過規定時間不能完成任務,全部人都會困死於此。看來,只有硬著頭皮去闖,才有可能離開這裏,並且探索出這個迷宮的秘密了。 於是大家馬上散開,很快摸清了這裏的地形,剩下的任務就是轟擊石頭了。那麽。。。論攻擊力最高的,自然非功夫DP莫屬,而且功夫DP可以使用前滾翻移動法,能夠瞬間獲得巨大的初速度,並且在直線運動的時候速度將近似光速,質量無窮大,那動能自然就。。。。。。DP每次可以選擇朝一個方向滾動,並且可以自己選擇在某位置停下來,或者撞擊到墻和石頭的時候被迫停下來。由於直線速度過快,所以要停下來拐彎自然就是很麻煩的事情。那麽只有制定出一個最好的運動方法,使得DP停下來次數最少,才能爭取盡量多的時間!

Input
第一行3個正整數N、M和T。表示這是一個NM的迷宮,並且有T個機關石。 接下來用一個NM的字符矩陣描述迷宮,.表示是空地,#表示是墻。 接下來T行每行2個正整數X、Y,描述一個機關石的位置,它在迷宮對應的位置是#。不會有兩個機關石在同一位置。 最後一行2個正整數X0、Y0,表示DP的初始位置。

Output
一個正整數ANS,表示DP至少要停下來多少次才能撞擊完所有的機關石。

Sample Input
4 6 3
……
….#.
…..#
….#.
2 5
3 6
4 5
1 5

Sample Output
5

HINT
數據規模:
對於10%的數據,N、M<=10,T<=2;

對於40%的數據,N、M<=50,T<=10;
對於100%的數據,N、M<=100,T<=15;
註意事項:
迷宮的最外層是墻,即任何時候不可能滾出迷宮,墻是撞不爛的(好硬)!
每次DP只能選擇4個基本方向中的一個方向移動,每塊機關石都必須被撞擊,撞擊後變成普通的墻。

我們可以設f[x][y][sta][k]代表當前在(x,y)這個點,已撞機關石的狀態為sta,當前朝向為k的最小停下次數。但這樣顯然是會T的。我們發現一個機關石只有周圍四個點是有意義的,因此我們可以用bfs預處理出這4T個點之間最少轉向次數,最後狀壓dp即可。復雜度\(O(4T*4NM+2^T*4T*4T)\)

ps:預處理操作裏有點騷,我會加註解

/*program from Wolfycz*/
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
#define p(x,y) ((x-1)*4+y+1)
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline int read(){
    int x=0,f=1;char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar())  if (ch=='-')    f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar())    x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}
inline void print(int x){
    if (x>=10)  print(x/10);
    putchar(x%10+'0');
}
const int N=16,M=1e2;
const int dx[4]={0,0,-1,1};
const int dy[4]={-1,1,0,0};
struct S1{int x,y;}h[M*M+10];
int dis[(N<<2)+10][(N<<2)+10],f[(1<<N)+10][(N<<2)+10];//dis記錄的4T個位置的相互距離,距離的具體定義參見SPFA結尾部分
int Ax[N+10],Ay[N+10],d[M+10][M+10][5];
bool map[M+10][M+10],vis[M+10][M+10];
char s[M+10];
int n,m,T;
bool in_map(int x,int y){return x>0&&x<=n&&y>0&&y<=m;}
void SPFA(int x,int y,int ID){//SPFA可能稍慢,但是能過
    if (!in_map(x,y)||map[x][y])    return;//非法判掉
    int head=0,tail=1;
    memset(d,63,sizeof(d));
    h[1]=(S1){x,y},vis[x][y]=1;
    for (int k=0;k<4;k++)   d[x][y][k]=0;//d[x][y][k]記錄在(x,y)這個點,朝向為k的最小轉向次數
    while (head!=tail){
        if (++head>M*M) head=1;
        int Nx=h[head].x,Ny=h[head].y;
        for (int k=0;k<4;k++){//枚舉(Nx,Ny)的朝向
            int tx=Nx+dx[k],ty=Ny+dy[k];
            if (!in_map(tx,ty)||map[tx][ty])    continue;
            for (int l=0;l<4;l++){//枚舉(tx,ty)的朝向
                if (d[tx][ty][l]>d[Nx][Ny][k]+(k!=l)){
                    d[tx][ty][l]=d[Nx][Ny][k]+(k!=l);//(k!=l)即為轉向一次
                    if (vis[tx][ty])    continue;
                    if (++tail>M*M) tail=1;
                    h[tail]=(S1){tx,ty};
                    vis[tx][ty]=1;
                }
            }
        }
        vis[Nx][Ny]=0;
    }
    for (int i=1;i<=T;i++){
        for (int k=0;k<4;k++){//枚舉第i個機關石的四周位置(tx,ty)
            int res=inf,tx=Ax[i]+dx[k],ty=Ay[i]+dy[k];
            for (int l=0;l<4;l++)   res=min(res,d[tx][ty][l]+(tx+dx[l]!=Ax[i]||ty+dy[l]!=Ay[i]));
            //l枚舉(tx,ty)的朝向,至於為什麽不是朝向機關石就要+1,因為dis[i][j]記錄的是從i出發到j,且面向機關石的轉向次數,因此要+1
            dis[ID][p(i,k)]=res;
        }
    }
}
int dp(){
    memset(f,127,sizeof(f));
    f[0][(T<<2)+1]=0;//起點位置
    for (int sta=0;sta<1<<T;sta++)
        for (int i=1;i<=(T<<2)+1;i++)
            if (f[sta][i]<inf)
                for (int j=1;j<=(T<<2)+1;j++)
                    f[sta|(1<<((j-1)>>2))][j]=min(f[sta|(1<<((j-1)>>2))][j],f[sta][i]+dis[i][j]+1);//枚舉從一個點到另一個點,要加上撞那一下的代價
    int Ans=inf;
    for (int i=1;i<=T<<2;i++)   Ans=min(Ans,f[(1<<T)-1][i]);//枚舉結束點
    return Ans;
}
int main(){
    n=read(),m=read(),T=read();
    memset(dis,63,sizeof(dis));
    for (int i=1;i<=n;i++){
        scanf("%s",s+1);
        for (int j=1;j<=m;j++)  if (s[j]=='#')  map[i][j]=1;
    }
    for (int i=1;i<=T;i++)  Ax[i]=read(),Ay[i]=read();
    for (int i=1;i<=T;i++)
        for (int k=0;k<4;k++)
            SPFA(Ax[i]+dx[k],Ay[i]+dy[k],p(i,k));
    int bx=read(),by=read();
    SPFA(bx,by,(T<<2)+1);//把起點也看成是某個機關石的周圍點
    printf("%d\n",dp());
    return 0;
}

[BZOJ 1556]墓地秘密