zoukankan      html  css  js  c++  java
  • [BZOJ1556]墓地秘密

    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
    ",dp());
    	return 0;
    }
    
  • 相关阅读:
    XML 验证器
    XML 浏览器支持
    XML 元素
    查看 XML 文件
    XML 属性
    Android入门之Activity四种启动模式
    XML 简介
    XML 语法规则
    [Android]Thread线程入门3多线程
    XML 验证
  • 原文地址:https://www.cnblogs.com/Wolfycz/p/9651997.html
Copyright © 2011-2022 走看看